├── .github ├── FUNDING.yml ├── vri.png └── workflows │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin └── dragon ├── development_install.sh ├── docs ├── Makefile ├── make.bat ├── requirements.txt ├── source │ ├── _static │ │ ├── css │ │ │ └── custom.css │ │ ├── logo-dark.png │ │ └── logo-light.png │ ├── commands.rst │ ├── conf.py │ ├── dragonmake.rst │ ├── images │ │ ├── dragon-quickstart-1.png │ │ ├── dragonmake-filter.jpg │ │ ├── dragonmake-module.jpg │ │ └── dragonmake-package.jpg │ ├── index.rst │ ├── objcs.rst │ ├── quickstart.rst │ ├── setup.rst │ ├── structure.rst │ └── theos.rst └── table_generator.py ├── setup.py └── src ├── buildgen ├── __init__.py ├── generator.py ├── makefile_generator.py ├── ninja_generator.py └── writer.py ├── dragon ├── __init__.py ├── config │ ├── banner.txt │ ├── defaults.yml │ ├── rules.yml │ ├── state.yml │ ├── targets.yml │ ├── tests.yml │ └── types.yml ├── device.py ├── editor.py ├── lo.py ├── prebuild.py ├── shscripts │ ├── building │ ├── dragoncolors │ ├── generator │ ├── packaging │ ├── prerun_checks │ ├── remote │ ├── simulator │ ├── upgrader │ ├── util │ └── variables ├── test.py ├── update_check.py ├── util.py └── wizard.py ├── dragongen ├── __init__.py ├── bfilter.py ├── cliutils.py ├── control.py ├── generation.py ├── theos.py ├── toolchain.py ├── util.py └── variable_types.py └── shared └── util.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: https://give.thetrevorproject.org/fundraiser/4567595 4 | -------------------------------------------------------------------------------- /.github/vri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/.github/vri.png -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Tests 5 | 6 | on: 7 | push: 8 | paths-ignore: 9 | - '**/*.md' 10 | - '**/*.txt' 11 | pull_request: 12 | paths-ignore: 13 | - '**/*.md' 14 | - '**/*.txt' 15 | 16 | jobs: 17 | tests: 18 | strategy: 19 | matrix: 20 | project-link: 21 | - https://github.com/kritanta-ios-tweaks/Chapters 22 | - https://github.com/kritanta-ios-tweaks/Pivot 23 | - https://github.com/kritanta-ios-tweaks/Shakelight 24 | - https://github.com/kritanta-ios-tweaks/Gravitation 25 | - https://github.com/kritanta-ios-tweaks/Signe 26 | - https://github.com/kritanta-ios-tweaks/StatusViz 27 | - https://github.com/kritanta-ios-tweaks/DeadRinger 28 | 29 | runs-on: macos-latest 30 | steps: 31 | - name: Set up Python 32 | uses: actions/setup-python@v4 33 | with: 34 | python-version: '3.10' 35 | 36 | - name: Install dependencies 37 | run: | 38 | # Checkout relevant repo (e.g., main repo branch or fork) 39 | REPO=${{ github.event.pull_request.head.repo.full_name }} 40 | REF=${{ github.event.pull_request.head.ref }} 41 | 42 | if [[ -z "$REPO" ]]; then 43 | REPO=${{ github.repository }} 44 | REF=${{ github.ref }} 45 | fi 46 | 47 | python -m pip install --upgrade pip 48 | pip install "git+https://github.com/$REPO@$REF" 49 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 50 | 51 | - name: Install dragon 52 | run: | 53 | dragon 54 | 55 | - name: Clone project 56 | run: | 57 | git init . 58 | git remote add origin ${{ matrix.project-link }} 59 | git pull origin $(git remote show origin | grep "HEAD branch" | sed 's/.*: //') 60 | 61 | - name: Build project 62 | run: | 63 | dragon c b 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ./.dragon 2 | .vscode 3 | .ccls-cache 4 | ./lib/HueSDK_iOS.framework 5 | .DS_Store 6 | bin/lib/Logos/Generator/Base/Generator.pm 7 | bin/tbdump 8 | DragonGen/__pycache__ 9 | state/nosshkey 10 | state/phoneip 11 | state/remoteip 12 | testing/* 13 | dist/ 14 | build/ 15 | dragon.egg-info/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Sky 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | Logo 5 | 6 |

7 |

8 | 9 | An expansible build system built for speed and ease of use. 10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 | Documentation 26 | | 27 | 28 | Getting Started 29 | | 30 | 31 | Example Project 32 | | 33 | 34 | Github Actions Integration 35 | 36 |

37 | 38 | # Installing dragon 39 | 40 | In your terminal: 41 | 42 | `pip3 install dragon` 43 | 44 | ## Updating 45 | 46 | Version 1.6.0 and later: 47 | 48 | ```sh 49 | dragon update 50 | ``` 51 | 52 | To reinstall a broken installation: 53 | 54 | ```sh 55 | rm -rf ~/.dragon 56 | pip3 install --force-reinstall dragon 57 | ``` 58 | 59 | --- 60 | 61 |

62 | vri 63 |

64 |

65 | a project by cynder 66 |

67 |

68 | brought to reality with the ongoing help of so many others. 69 |

70 | 71 | --- 72 | 73 |

74 | Continued in honor of Lorenzo Pane, without whom this project, and so much else, would not have been possible. 75 |

76 | 77 |

78 | in lieu of personal donations/sponsorships, please direct donations to The Trevor Project 79 |

80 | -------------------------------------------------------------------------------- /bin/dragon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export DRAGON_DIR=.dragon 4 | export DRAGON_VERS=$(python3 -c 'from dragon.util import version; print(version())') 5 | 6 | # Default dragon root dir is ~/.dragon 7 | if [[ -z $DRAGON_ROOT_DIR ]]; then 8 | export DRAGON_ROOT_DIR=$HOME/$DRAGON_DIR 9 | fi 10 | 11 | TOOLPATH=$(python3 -c 'from dragon.util import tool_path; print(tool_path())') 12 | 13 | # Load in basic functions 14 | source $TOOLPATH/util 15 | 16 | # Run setup wizard if dragon root dir is empty or nonexistent 17 | if [[ -z "$(ls -A $DRAGON_ROOT_DIR 2> /dev/null)" ]]; then 18 | python3 -m dragon.wizard 19 | drexit 0 20 | fi 21 | 22 | # Check deps are good to go 23 | source $TOOLPATH/prerun_checks 24 | 25 | # Tool imports 26 | source $TOOLPATH/simulator 27 | source $TOOLPATH/packaging 28 | source $TOOLPATH/building 29 | source $TOOLPATH/generator 30 | source $TOOLPATH/remote 31 | 32 | # All "state" variables we set and then act upon. 33 | source $TOOLPATH/variables 34 | 35 | yaml() { 36 | python3 -c "import yaml;print(yaml.safe_load(open('$1'))$2)" 2> /dev/null 37 | } 38 | 39 | set -o pipefail 40 | 41 | # Define the local dragon data dir 42 | if [[ $PWD != $HOME ]]; then 43 | export DRAGON_DATA_DIR="$PWD/$DRAGON_DIR" 44 | fi 45 | 46 | # -------- 47 | # Argument Parsing 48 | # Set a variable here or call a bash function if it's a tool 49 | # -------- 50 | 51 | # If we have no args, print usage and exit 52 | if [[ -z $1 ]]; then 53 | usage 54 | drexit 55 | fi 56 | 57 | while ! [[ -z $1 ]]; do 58 | case $1 in 59 | # -------- 60 | # Set Variables 61 | # -------- 62 | do ) gen=1 63 | build=1 64 | install=1 65 | ;; 66 | u | uninstall ) shift 67 | uninstall_package $* 68 | drexit 69 | ;; 70 | g | gen | generate) gen=1 71 | ;; 72 | n | nic | new | edit | create ) python3 -m dragon.editor 73 | drexit 74 | ;; 75 | exp | export) gen=1 76 | exportt=1 77 | build=1 78 | ;; 79 | norm ) norm=1 80 | ;; 81 | b | build | make) gen=1 82 | build=1 83 | ;; 84 | sim | simulator ) export simtarg=1 85 | ;; 86 | i | install ) install=1 87 | ;; 88 | debug ) debug=1 89 | shift 90 | debugproc="$1" 91 | ;; 92 | ddebug ) NINJA_ARGS="-v" 93 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 94 | set -xv 95 | export DGEN_DEBUG=1 96 | ;; 97 | c | clean ) gen=1 98 | clean=1 99 | ;; 100 | vn ) NINJA_ARGS="-v" 101 | ;; 102 | vd ) export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 103 | set -xv 104 | ;; 105 | vg ) export DGEN_DEBUG=1 106 | ;; 107 | lo | objcs ) shift 108 | python3 -m dragon.lo $* 109 | drexit 110 | ;; 111 | 112 | # -------- 113 | # Commands 114 | # -------- 115 | up | update | upgrade ) source $TOOLPATH/upgrader 116 | drexit 117 | ;; 118 | rs | respring ) python3 -m dragon.device run sbreload 119 | drexit 120 | ;; 121 | dr | devicerun ) shift 122 | python3 -m dragon.device run $* 123 | drexit 124 | ;; 125 | s | device) python3 -m dragon.device setup 126 | ;; 127 | sr | rconf ) setupRemote 128 | ;; 129 | sn | send ) send_package $2 130 | drexit 131 | ;; 132 | r | release ) release=1 133 | ;; 134 | ro | rootless ) rootless=1 135 | PkgPrefix="/var/jb" 136 | ;; 137 | h | help | -h ) usage 138 | drexit 139 | ;; 140 | test ) python3 -m dragon.test 141 | drexit 142 | ;; 143 | time ) N=`date +%s%N`; export PS4='+[$(((`date +%s%N`-$N)/1000000))ms][${BASH_SOURCE}:${LINENO}]: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'; 144 | set -x 145 | ;; 146 | v | -v ) cat $DRAGON_ROOT_DIR/internal/banner.txt 147 | ;; 148 | * ) usage 149 | drexit 1 150 | 151 | esac # who comes up with this syntax lol 152 | shift 153 | done 154 | 155 | 156 | # -------- 157 | # Generate project info 158 | # -------- 159 | if [[ $gen -eq 1 ]]; then 160 | # Invoke the generator 161 | # shellcheck disable=SC2154 162 | rm -rf "$DRAGON_DATA_DIR/ninja/build.ninja" 2>/dev/null 163 | mkdir -p "$DRAGON_DATA_DIR/"{ninja,modules} 164 | 165 | generate 166 | 167 | if [[ $DRAGONGEN_FAILURE -eq 1 ]]; then 168 | cleanbuildfail 169 | fi 170 | fi 171 | 172 | 173 | get_subprojs() 174 | { 175 | set -f 176 | # If there's more than one project in the root directory we need to iterate by name. 177 | # Relying on generator to obtain project info 178 | # shellcheck disable=SC2207 179 | subsb=($(python3 -c "print('${project_dirs}' if ('${project_dirs}'.count('.')<2) else '${project_names}')")) 180 | proj_names=($(python3 -c "print('${project_names}')")) 181 | set +f 182 | index=0 183 | for i in "${subsb[@]}"; do 184 | echo "$i ${proj_names[$index]}" 185 | ((index++)) 186 | done 187 | } 188 | 189 | 190 | # -------- 191 | # Clean project(s) 192 | # -------- 193 | if [[ $clean -eq 1 ]]; then 194 | # clean subproj build dir(s) 195 | get_subprojs | while read -r i j; do 196 | clean_dir $i $j 197 | done 198 | # ensure clean primary build dir 199 | clean_dir . $(basename $PWD) 200 | fi 201 | 202 | 203 | # -------- 204 | # Build, then package (if needed) 205 | # -------- 206 | if [[ $build -eq 1 ]]; then 207 | mkdir -p "$DRAGON_DATA_DIR" 208 | cp DragonMake "$DRAGON_DATA_DIR/DragonMake" 2> /dev/null 209 | 210 | get_subprojs | while read -r i j; do 211 | # Copy the DragonMake into the subproject's build directory 212 | # Primarily for bundle filter gen, right now. 213 | if [[ -d $i ]]; then 214 | mkdir -p "$i/$DRAGON_DIR/" 215 | cp DragonMake "$i/$DRAGON_DIR/DragonMake" 2> /dev/null 216 | fi 217 | build $i $j 218 | done 219 | 220 | find . -name '.clean' -type f -delete 221 | 222 | if [[ $DRAGON_DPKG -eq 1 ]]; then 223 | create_package 224 | fi 225 | 226 | prefix_print "Cleaning Up" 227 | 228 | if [[ $norm -eq 0 ]]; then 229 | find . -name '*.ninja' -type f -delete 230 | fi 231 | fi 232 | 233 | 234 | # -------- 235 | # Install a package after building (doesn't need to be same session) 236 | # DRAGON_DPKG is 1 by default, modified by the generator 237 | # -------- 238 | if [[ $install -eq 1 && $DRAGON_DPKG -eq 1 ]]; then 239 | OUTPUT="$(cat "$DRAGON_DATA_DIR/last_package" | tr -d '\040\011\012\015' )" 240 | 241 | if [[ -z $OUTPUT ]]; then 242 | prefix_print "Packaging Failed" 243 | drexit 244 | fi 245 | 246 | send_package packages/$OUTPUT 247 | 248 | INSTALL_CMD=$(yaml DragonMake "['icmd']" || echo "sbreload") 249 | 250 | python3 -m dragon.device run ${INSTALL_CMD} || drexit 251 | fi 252 | 253 | 254 | # -------- 255 | # On-device debugging 256 | # -------- 257 | if [[ $debug -eq 1 ]]; then 258 | eval $(python3 -m dragon.device get) 259 | prefix_print "Starting on device debugger and targeting '$debugproc'" 260 | ssh -p $DRBPORT root@$DRBIP "command -v lldb || apt install lldb" || drexit 261 | ssh -p $DRBPORT root@$DRBIP "lldb -n $debugproc || killall lldb" || drexit 262 | fi 263 | 264 | 265 | rm -rf "$DRAGON_DATA_DIR/packages" > /dev/null 266 | set +xv 267 | 268 | python3 -m dragon.update_check 269 | -------------------------------------------------------------------------------- /development_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Quick Uninstall/Install script for use when working on the project 5 | # 6 | 7 | python3 -m pip uninstall --yes dragon 8 | python3 -m pip install . 9 | DRAGON_DIR=.dragon && DRAGON_ROOT_DIR=$HOME/$DRAGON_DIR \ 10 | DRAGON_VERS=$(python3 -c 'from dragon.util import version; print(version())') \ 11 | python3 -m dragon.wizard 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | 3 | # You can set these variables from the command line, and also 4 | # from the environment for the first two. 5 | SPHINXOPTS ?= 6 | SPHINXBUILD ?= sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx<6.0.0 2 | sphinx_rtd_theme<=2.0.0 3 | furo==2022.12.7 -------------------------------------------------------------------------------- /docs/source/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | .related-information, .related-information a { 2 | opacity: 0.4; 3 | color: white !important; 4 | text-decoration: none !important; 5 | } 6 | 7 | .sidebar-drawer { 8 | width: calc(41% - 26em); 9 | } 10 | 11 | tbody tr:first-of-type td { 12 | font-weight: 700; 13 | padding-bottom: .5em; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /docs/source/_static/logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/docs/source/_static/logo-dark.png -------------------------------------------------------------------------------- /docs/source/_static/logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/docs/source/_static/logo-light.png -------------------------------------------------------------------------------- /docs/source/commands.rst: -------------------------------------------------------------------------------- 1 | Commands 2 | --------------------- 3 | 4 | Running ``dragon`` without any arguments will list available commands, many of which have multiple aliases. 5 | 6 | You can combine most commands to do multiple actions with one command. 7 | 8 | 9 | Packaging Commands 10 | ===================== 11 | 12 | Creating a new project/module 13 | ********************* 14 | 15 | ``dragon n``, ``dragon new``, ``dragon nic``, ``dragon edit``, or ``dragon create`` will open the Project Editor 16 | 17 | 18 | Building a package 19 | ********************* 20 | 21 | ``dragon b``, ``dragon build``, or ``dragon make`` builds a package 22 | 23 | 24 | Building a package for release 25 | ^^^^^^^^^^^^^^^^^^^^^ 26 | 27 | The ``r`` / ``release`` command can be added to the ``build`` command to define "NDEBUG" and undefine "DEBUG" within compiled code. 28 | 29 | Passing this flag will also cause the contents of the DragonMake variable ``dbgflags`` to be ignored, and the contents of ``releaseflags`` to be used instead. 30 | 31 | 32 | Clean Building a package 33 | ********************* 34 | 35 | ``dragon c`` or ``dragon clean`` will clean the 'build cache' 36 | 37 | Combine it with the build command to run a clean build (e.g. ``dragon c b``) 38 | 39 | 40 | Device Commands 41 | ===================== 42 | 43 | Setting up a device 44 | ********************* 45 | 46 | ``dragon s`` or ``dragon device`` will set up an installation target 47 | 48 | 49 | Installing a package 50 | ********************* 51 | 52 | ``dragon i`` or ``dragon install`` installs a package 53 | 54 | Combine it with the build command, or use ``dragon do`` to build and install a package 55 | 56 | Respringing a device 57 | ********************* 58 | 59 | ``dragon rs`` or ``dragon respring`` will respring the current device (i.e. current installation target) 60 | 61 | 62 | Running a command on the device 63 | ********************* 64 | ``dragon dr `` or ``dragon devicerun `` will execute anything after the command on the current device (i.e. current installation target) [don't use quotes] 65 | 66 | 67 | Installing any deb on the device 68 | ********************* 69 | 70 | ``dragon sn `` or ``dragon send `` will install a ``.deb`` anywhere on your drive to the current device (i.e. current installation target) 71 | 72 | 73 | Building and installing to the iOS Simulator 74 | ********************* 75 | 76 | Adding the ``sim`` command to a set of commands targets the simulator. If added to an ``install`` command, it will install the specified deb to the iOS simulator 77 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file only contains a selection of the most common options. For a full 6 | # list see the documentation: 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os, sys 16 | 17 | sys.path.insert(0, os.path.abspath('../../')) 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = 'dragon' 22 | copyright = '2023, cynder' 23 | author = 'cynder' 24 | 25 | # The full version, including alpha/beta/rc tags 26 | release = '1.7.3' 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # List of patterns, relative to source directory, that match files and 39 | # directories to ignore when looking for source files. 40 | # This pattern also affects html_static_path and html_extra_path. 41 | exclude_patterns = [] 42 | 43 | # -- Options for HTML output ------------------------------------------------- 44 | 45 | # The theme to use for HTML and HTML Help pages. See the documentation for 46 | # a list of builtin themes. 47 | # 48 | html_theme = 'furo' 49 | 50 | html_theme_options = { 51 | "sidebar_hide_name": True, 52 | "navigation_with_keys": True, 53 | "light_logo": "logo-light.png", 54 | "dark_logo": "logo-dark.png", 55 | } 56 | 57 | html_css_files = [ 58 | 'css/custom.css', 59 | ] 60 | 61 | # Add any paths that contain custom static files (such as style sheets) here, 62 | # relative to this directory. They are copied after the builtin static files, 63 | # so a file named "default.css" will overwrite the builtin "default.css". 64 | html_static_path = ['_static'] 65 | -------------------------------------------------------------------------------- /docs/source/dragonmake.rst: -------------------------------------------------------------------------------- 1 | The DragonMake Format 2 | --------------------- 3 | 4 | Intead of splitting up build instructions among a ton of 'Makefile's, dragon build variables are all declared in a single `DragonMake` file at the root of the project. 5 | 6 | DragonMake files use YAML syntax. 7 | 8 | .. code-block:: YAML 9 | 10 | name: DemoTweak 11 | id: me.cynder.dragondemo 12 | depends: mobilesubstrate 13 | architecture: iphoneos-arm 14 | description: Demo Tweak 15 | author: cynder 16 | section: Tweaks 17 | 18 | DemoTweak: 19 | type: tweak 20 | filter: 21 | executables: 22 | - SpringBoard 23 | files: 24 | - DemoTweak.x 25 | 26 | 27 | The Project 28 | ********************* 29 | 30 | The full `DragonMake` represents the "Project", which contains one or more "Modules" (tweaks, prefs, etc). 31 | 32 | .. code-block:: YAML 33 | 34 | name: DemoTweak 35 | id: me.cynder.dragondemo 36 | depends: mobilesubstrate 37 | architecture: iphoneos-arm 38 | description: Demo Tweak 39 | author: cynder 40 | section: Tweaks 41 | 42 | Variables 43 | ===================== 44 | 45 | .. list-table:: 46 | :widths: 5 1 10 47 | 48 | * - Variable 49 | - Type 50 | - Description 51 | * - name 52 | - String 53 | - Name of the project 54 | * - icmd 55 | - String 56 | - (Optional) Command to run after installation on the target device 57 | 58 | `control` Variables 59 | ===================== 60 | 61 | If your project already has a `control` file you don't need to worry about these. 62 | 63 | .. list-table:: 64 | :widths: 5 1 10 65 | 66 | * - Variable 67 | - Type 68 | - Description 69 | * - id 70 | - String 71 | - Bundle ID (e.g. me.cynder.demotweak) for the Project 72 | * - author 73 | - String 74 | - Author of the project. Current account's username will be used if none is provided 75 | * - description 76 | - String 77 | - Description of the package 78 | * - version 79 | - String 80 | - Version of the project 81 | * - section 82 | - String 83 | - Section to place this tweak in. (e.g. 'Tweaks') 84 | * - depends 85 | - String 86 | - Comma separated list of bundle ids this package depends on 87 | * - maintainer 88 | - String 89 | - (Optional) Maintainer of the project. Will use the value of 'author' if none is provided 90 | * - provides 91 | - String 92 | - (Optional) Comma separated list of bundle ids this package provides 93 | 94 | 95 | Debian Package Script Variables 96 | ===================== 97 | 98 | Lists of commands can be specified with `preinst:`, `postinst:`, `prerm:` and/or `postrm:` to create packaging scripts included in the binary. 99 | 100 | .. code-block:: YAML 101 | 102 | name: DemoTweak 103 | id: me.cynder.dragondemo 104 | depends: mobilesubstrate 105 | architecture: iphoneos-arm 106 | description: Demo Tweak 107 | author: cynder 108 | section: Tweaks 109 | # This will run on the device after installation 110 | postinst: 111 | - echo "Hello from dragon!" 112 | 113 | Modules 114 | ********************* 115 | 116 | Modules in the `DragonMake` represent individual components of your package. 117 | 118 | These include things like a Tweak, Preferences, etc. 119 | 120 | .. code-block:: YAML 121 | 122 | DemoTweak: 123 | type: tweak 124 | filter: 125 | executables: 126 | - SpringBoard 127 | files: 128 | - DemoTweak.x 129 | 130 | 131 | The "Important" Variables 132 | ===================== 133 | 134 | .. list-table:: 135 | :widths: 5 1 10 136 | 137 | * - Variable 138 | - Type 139 | - Description 140 | * - type 141 | - String 142 | - Project type -- see next section 143 | * - dir 144 | - String 145 | - (Optional) Subdirectory the files are located in, if they're in one 146 | * - files 147 | - List 148 | - List of files in the project to be compiled 149 | 150 | Types 151 | ^^^^^^^^^^^^^^^^^^^^^ 152 | 153 | .. list-table:: 154 | :widths: 5 10 155 | 156 | * - Type 157 | - Description 158 | * - app 159 | - Build an application for jailbroken devices 160 | * - tweak 161 | - Build a tweak for jailbroken devices 162 | * - prefs 163 | - Build a preference bundle 164 | * - bundle 165 | - Build some other type of bundle 166 | * - resource-bundle 167 | - Build a bundle containing only resources 168 | * - framework 169 | - Build a framework 170 | * - library 171 | - Build a library 172 | * - cli 173 | - Build a CLI tool/binary 174 | * - static 175 | - Build a static library 176 | * - stage 177 | - Module containing only a stage variable 178 | 179 | 180 | Tweak bundle filters 181 | ^^^^^^^^^^^^^^^^^^^^^ 182 | Bundle filters tell MobileSubstrate (or whatever injection system your jailbreak uses) what processes to inject your tweak into. 183 | 184 | dragon supports the standard Theos format, but allows specifying the values in the `DragonMake`, if you want. 185 | 186 | .. code-block:: YAML 187 | 188 | DemoTweak: 189 | type: tweak 190 | # This bit 191 | filter: 192 | executables: 193 | - SpringBoard 194 | 195 | files: 196 | - DemoTweak.x 197 | 198 | 199 | .. 200 | todo: info about files: stuff 201 | 202 | 203 | Common Module variables 204 | ===================== 205 | 206 | None of these are required by default, but you may need some of them for various projects. 207 | 208 | .. list-table:: 209 | :widths: 5 1 10 210 | 211 | * - Variable 212 | - Type 213 | - Description 214 | * - archs 215 | - List 216 | - List of archs to compile for 217 | * - cflags 218 | - String/List 219 | - List (or a space seperated string) with cflags used at compilation time 220 | * - releaseflags 221 | - String/List 222 | - List (or a space seperated string) with cflags used on release (dragon b r) builds 223 | * - dbgflags 224 | - String/List 225 | - List (or a space seperated string) with cflags used on debug builds (without r/release command) 226 | * - frameworks 227 | - List 228 | - List of frameworks to link against 229 | * - libs 230 | - List 231 | - List of libraries to link against 232 | * - entfile 233 | - String 234 | - File containing entitlements to codesign the module with 235 | * - include 236 | - List 237 | - List of directories to search for headers in 238 | * - additional_fw_dirs 239 | - List 240 | - List of additional directories to search for frameworks in 241 | * - additional_lib_dirs 242 | - List 243 | - List of additional directories to search for libraries in 244 | * - prefix 245 | - List 246 | - List of headers to be imported into ALL files at compilation time 247 | * - for 248 | - String 249 | - Sets the target OS to build for [ios, watchos, host(macos)] 250 | * - arc 251 | - Boolean 252 | - Enable ARC (Default: YES) 253 | * - sysroot 254 | - String 255 | - Specify Directory the SDK is located in 256 | * - targetvers 257 | - String 258 | - Version of the OS to target 259 | * - macros 260 | - List 261 | - List of declaration flags (-D) to add to the compilation flags 262 | 263 | 264 | Setting Module Defaults 265 | ===================== 266 | 267 | A special module can be specified with the name `all:`; its variables will be set as the "default" value for all Modules in the project. 268 | 269 | If a Module specifies a different value than `all:`, it'll override the one declared in `all:`. 270 | -------------------------------------------------------------------------------- /docs/source/images/dragon-quickstart-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/docs/source/images/dragon-quickstart-1.png -------------------------------------------------------------------------------- /docs/source/images/dragonmake-filter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/docs/source/images/dragonmake-filter.jpg -------------------------------------------------------------------------------- /docs/source/images/dragonmake-module.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/docs/source/images/dragonmake-module.jpg -------------------------------------------------------------------------------- /docs/source/images/dragonmake-package.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/docs/source/images/dragonmake-package.jpg -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | dragon 2 | ================================= 3 | 4 | 5 | "dragon" is a build system primarily targeting jailbroken iOS devices, capable of building tweaks, preferences, frameworks, apps, and anything else related to them. 6 | 7 | It's designed to be simple, both in installation and usage, and to be hackable and configurable at every step of the way. 8 | 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | :caption: Contents: 13 | 14 | setup 15 | commands 16 | quickstart 17 | dragonmake 18 | structure 19 | theos 20 | -------------------------------------------------------------------------------- /docs/source/objcs.rst: -------------------------------------------------------------------------------- 1 | Objective-CS and the llvm-objcs Toolchin 2 | --------------------- 3 | 4 | Dragon ships with builtin integration for the llvm-objcs toolchain and the Objective-CS hooking language. 5 | 6 | It provides wrappers, utilities, and commands that help set up the toolchain and get autocomplete, editor support, etc working with clangd. 7 | 8 | A companion extension for vscode exists at [LINKHERE] 9 | 10 | Adding support to a project should be fairly drop-in. Projects using Objective-CS can still use logos, they will just need 11 | to be in separate files for autocomplete, etc to work. 12 | 13 | Objective-CS 14 | ********************* 15 | 16 | Objective-CS is an extension of Objective-C designed to provide easier integration with hooking APIs than working in purely Objective-C. 17 | 18 | By building our language directly within llvm as opposed to via a preprocessor (logos), we gain access to a large amount of 19 | existing tooling that already supports LLVM. 20 | 21 | This allows autocomplete, inline error messages/help/suggestions, and the myriad other clangd features to work within 22 | Objective-CS files. 23 | 24 | 25 | File Extension 26 | ===================== 27 | 28 | Currently, Objective-CS just adds support directly into Objective-C code, so you use the same .m or .mm extension as regular 29 | objc. Eventually it may be gated or aliased to .mx/.mmx. 30 | 31 | Basic Syntax 32 | ===================== 33 | 34 | The following code block demos the full current featureset of Objective-CS 35 | 36 | .. code-block:: objc 37 | 38 | // Predeclaring the interface for what we're hoooking isn't required, 39 | // but it allows us to: 40 | // autocomplete hook selectors 41 | // access ivars of the class we're hooking 42 | // declare new methods we want to add 43 | @interface SBIconView : UIView 44 | { 45 | BOOL _allowsLabelArea; // Declare ivars we want to "hook" (access) here 46 | CGFloat _iconImageAlpha; // This replaces the need for swapping to ObjC++ and wrangling the MSHookIvar API. 47 | } 48 | 49 | -(void)configureForLabelAllowed:(BOOL)allowed; 50 | 51 | @new 52 | -(void)myAwesomeNewInstanceMethod; 53 | 54 | @end 55 | 56 | // If you've already imported a header for a given class (from a patched SDK that has proper headers), 57 | // you can instead declare a category ( `@interface SBIconView ()` ) and still utilize these features. 58 | // This accomplishes the same thing in terms of 59 | // applying new methods, accessing ivars that maybe didn't exist in the header you imported, etc. 60 | 61 | // -- 62 | 63 | @group iOS13Plus 64 | @hook SBIconView 65 | 66 | -(void)myAwesomeNewInstanceMethod 67 | { 68 | NSLog(@"Hello from Objective-CS!"); 69 | } 70 | 71 | -(void)configureForLabelAllowed:(BOOL)allowed 72 | { 73 | @orig(NO); 74 | _allowsLabelArea = NO; 75 | _iconImageAlpha = 0.5; 76 | [self myAwesomeNewInstanceMethod]; 77 | } 78 | 79 | @end 80 | @end 81 | 82 | #ifndef kCFCoreFoundationVersionNumber_iOS_13_0 83 | #define kCFCoreFoundationVersionNumber_iOS_13_0 1665.15 84 | #endif 85 | 86 | // This is a manually declared constructor. This is not required if you are not using groups. 87 | // You *will* need to use one if you are using groups, as all groups must be initialized for their hooks to be applied. 88 | // _eventually,_ something like @ctor (analogous to logos' %ctor) may be added. 89 | __attribute__((constructor)) 90 | void initFunc(void) 91 | { 92 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_13_0){ 93 | @init(iOS13Plus); 94 | // initialize multiple groups: @init(myGroup, mySecondGroup, andSoOn); 95 | } 96 | } 97 | 98 | 99 | Future 100 | ===================== 101 | 102 | Plans exist to add support for: 103 | * `@hookf(FunctionName)` 104 | * `@ctor{}` 105 | * `@subclass` 106 | 107 | This is a hobby project with one developer, so there is no timeframe on these plans :) 108 | 109 | llvm-objcs 110 | ********************* 111 | 112 | LLVM-objcs is a fork of Apple's LLVM that supports compiling Objective-CS code. 113 | 114 | It aims to support the same featureset as apple-llvm, however modules have been an undocumented pain to compile support for, 115 | so the currently distributed binaries do not yet support them (i.e. `@import UIKit;`. Just import headers normally for now). 116 | 117 | Builds are available in arm64 and x86_64 form for macOS, linux, and iOS (iOS is arm64 only, silly) 118 | 119 | Installing 120 | ===================== 121 | 122 | `dragon lo setup` will download and install the appropriate toolchain for your system. 123 | 124 | It will also be automatically installed if a DragonMake project declares it as required, and it isn't already installed. 125 | 126 | Manually 127 | ^^^^^^^^^^^^^^^^^^^^^ 128 | 129 | Download or build the appropriate toolchain and extract/install it in `~/.dragon/llvm-objcs`. The directory structure 130 | should be as follows: 131 | 132 | .. code-block:: bash 133 | 134 | serket@prospit ~ % tree ~/.dragon/llvm-objcs -L 1 135 | /Users/serket/.dragon/llvm-objcs 136 | ├── bin 137 | ├── include 138 | ├── lib 139 | ├── libexec 140 | └── share 141 | 142 | After that you're good to go. 143 | 144 | Future 145 | ===================== 146 | 147 | As this toolchain was built off of llvm-17, it does not support arm64e libraries injected into arm64e processes for pre-iOS 14 devices. 148 | 149 | This is an issue with all toolchains post llvm-12, and workarounds are being looked into. 150 | -------------------------------------------------------------------------------- /docs/source/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quick-Start Guide 2 | --------------------- 3 | 4 | After completing the setup, getting started with dragon is easy. 5 | 6 | 7 | Creating your first project:: 8 | 9 | dragon n 10 | 11 | This opens the dragon project editor 12 | 13 | .. image:: images/dragon-quickstart-1.png 14 | :width: 600 15 | 16 | 17 | Building your project:: 18 | 19 | dragon b 20 | 21 | 22 | Installing your project:: 23 | 24 | dragon i 25 | 26 | 27 | You can do both of these at the same time; most commands in dragon can be combined:: 28 | 29 | dragon b i 30 | 31 | 32 | Or you can use the shorthand notation:: 33 | 34 | dragon do 35 | 36 | Building and installing to the iOS Simulator:: 37 | 38 | dragon b i sim 39 | -------------------------------------------------------------------------------- /docs/source/setup.rst: -------------------------------------------------------------------------------- 1 | Setup 2 | --------------------- 3 | 4 | Installing 5 | ********************* 6 | 7 | Installing is incredibly simple:: 8 | 9 | pip3 install dragon 10 | 11 | Type "dragon" in your terminal to complete the initial setup 12 | 13 | Updating 14 | ********************* 15 | 16 | Versions 1.6.0 and later:: 17 | 18 | dragon update 19 | 20 | Updating from earlier versions:: 21 | 22 | rm -rf ~/.dragon 23 | pip3 install --force-reinstall dragon 24 | dragon 25 | 26 | -------------------------------------------------------------------------------- /docs/source/structure.rst: -------------------------------------------------------------------------------- 1 | Structure 2 | --------------------- 3 | 4 | dragon is set up such that the resources you need are provided via submodules and additional resources can be added as desired. 5 | 6 | frameworks/: 7 | A place for frameworks (.framework) [uses .tbd format] 8 | include/: 9 | A place for headers (.h) 10 | internal/: 11 | A place for YAML configuration files (.yml) [not meant to be edited, but feel free to get your hands dirty] 12 | lib/: 13 | A place for libraries [uses .dylib or .tbd format] 14 | sdks/: 15 | A place for SDKs (.sdk) [should be patched to include private frameworks] 16 | src/: 17 | A place for out-sourced tools modified and built for use with dragon 18 | toolchain/: 19 | A place for a user-provided toolchain [unnecessary on Darwin platforms] 20 | -------------------------------------------------------------------------------- /docs/source/theos.rst: -------------------------------------------------------------------------------- 1 | Theos Support 2 | --------------------- 3 | 4 | dragon aims to provide as much compatibility with theos projects and their structure as possible. 5 | 6 | 7 | *control* files, Bundle filters, etc. 8 | ===================== 9 | 10 | dragon ships with support for these in both Theos Makefile and DragonMake format projects. 11 | 12 | 13 | Makefile interpreter 14 | ===================== 15 | 16 | dragon includes a best-effort Makefile "interpreter" that attempts to translate as much from standard Theos project structure as possible. 17 | 18 | It also includes several support files used with Theos projects. 19 | 20 | Compiling a Theos project should be as simple as:: 21 | 22 | dragon b 23 | 24 | If you encounter any issues with it, feel free to file an issue on https://github.com/DragonBuild/dragon. 25 | -------------------------------------------------------------------------------- /docs/table_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | ''' 4 | Quick rST syntax table generator i threw together 5 | ''' 6 | 7 | while True: 8 | size = int(input('Size > ')) 9 | 10 | fields = [] 11 | 12 | widths = [] 13 | 14 | for i in range(0, size): 15 | widths.append(input(f'Size {i} > ')) 16 | 17 | for i in range(0, size): 18 | fields.append(input(f'Field Name {i} > ')) 19 | 20 | text = '.. list-table::\n :widths: ' + ' '.join(widths) + '\n\n' 21 | 22 | fo = True 23 | for field in fields: 24 | text += f' {"*" if fo else " "} - {field}\n' 25 | if fo: 26 | fo = False 27 | 28 | while True: 29 | field_vals = [] 30 | breakOut = False 31 | for field in fields: 32 | inp = input(f'{field} > ') 33 | if inp == 'done': 34 | breakOut=True 35 | break 36 | field_vals.append(inp) 37 | if breakOut: 38 | break 39 | f = True 40 | for val in field_vals: 41 | text += f' {"*" if f else " "} - {val}\n' 42 | if f: 43 | f = False 44 | 45 | print(text) 46 | 47 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from setuptools import setup 4 | 5 | setup(name='dragon', 6 | version='2.0.0', 7 | description='A powerful toolkit targeting Apple development, research, and packaging.', 8 | author='cynder', 9 | url='https://dragon.cynder.me/', 10 | install_requires=['ninja', 'pyyaml', 'ruyaml', 'packaging', 'tqdm'], 11 | packages=['dragon', 'dragongen', 'buildgen', 'shared'], 12 | package_dir={ 13 | 'dragon': 'src/dragon', 14 | 'dragongen': 'src/dragongen', 15 | 'buildgen': 'src/buildgen', 16 | 'shared': 'src/shared', 17 | }, 18 | package_data={ 19 | 'dragon': ['shscripts/*', 'config/*'], 20 | }, 21 | scripts=['bin/dragon'] 22 | ) 23 | -------------------------------------------------------------------------------- /src/buildgen/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/src/buildgen/__init__.py -------------------------------------------------------------------------------- /src/buildgen/generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """GenBuild aims to provide a python wrapper for creating Makefile/Build.ninja files 4 | with standard syntax. 5 | """ 6 | from enum import Enum 7 | from .makefile_generator import MakefileWriter 8 | from .ninja_generator import NinjaWriter 9 | 10 | 11 | class BuildSystem(Enum): 12 | NINJA = 0 13 | MAKE = 1 14 | 15 | 16 | class BuildFileGenerator(object): 17 | def __init__(self, output, build_type=BuildSystem.NINJA): 18 | self.builder = NinjaWriter(output) if build_type == BuildSystem.NINJA else MakefileWriter(output) 19 | 20 | def newline(self): 21 | """Add a newline 22 | 23 | """ 24 | self.builder.newline() 25 | 26 | def comment(self, text): 27 | """ 28 | 29 | :param text: 30 | """ 31 | self.builder.comment(text) 32 | 33 | def variable(self, key, value, indent=0): 34 | """ 35 | 36 | :param key: 37 | :param value: 38 | :param indent: 39 | :return: 40 | """ 41 | self.builder.variable(key, value, indent) 42 | 43 | def pool(self, name, depth): 44 | """ 45 | 46 | :param name: 47 | :param depth: 48 | """ 49 | self.builder.pool(name, depth) 50 | 51 | def rule(self, name, command, description=None, depfile=None, 52 | generator=False, pool=None, restat=False, rspfile=None, 53 | rspfile_content=None, deps=None): 54 | """ 55 | 56 | :param name: 57 | :param command: 58 | :param description: 59 | :param depfile: 60 | :param generator: 61 | :param pool: 62 | :param restat: 63 | :param rspfile: 64 | :param rspfile_content: 65 | :param deps: 66 | """ 67 | self.builder.rule(name, command, description, depfile, 68 | generator, pool, restat, rspfile, 69 | rspfile_content, deps) 70 | 71 | def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, 72 | variables=None, implicit_outputs=None, pool=None): 73 | """ 74 | 75 | :param outputs: 76 | :param rule: 77 | :param inputs: 78 | :param implicit: 79 | :param order_only: 80 | :param variables: 81 | :param implicit_outputs: 82 | :param pool: 83 | :return: 84 | """ 85 | return self.builder.build(outputs, rule, inputs, implicit, order_only, variables, implicit_outputs, pool) 86 | 87 | def include(self, path): 88 | """ 89 | 90 | :param path: 91 | """ 92 | self.builder.include(path) 93 | 94 | def subfile(self, path): 95 | """ 96 | 97 | :param path: 98 | """ 99 | self.builder.subninja(path) 100 | 101 | def default(self, paths: list): 102 | """This should specify the default build targets 103 | 104 | :param paths: 105 | """ 106 | self.builder.default(paths) 107 | 108 | def close(self): 109 | self.builder.close() 110 | 111 | 112 | def as_list(unknownTypeInput): 113 | """ 114 | 115 | :param unknownTypeInput: 116 | :return: 117 | """ 118 | if unknownTypeInput is None: 119 | return [] 120 | if isinstance(unknownTypeInput, list): 121 | return unknownTypeInput 122 | return [unknownTypeInput] 123 | 124 | 125 | def escape(string): 126 | """Escape a string such that it can be embedded into a Ninja file without 127 | further interpretation.""" 128 | assert '\n' not in string, 'Ninja syntax does not allow newlines' 129 | # We only have one special metacharacter: '$'. 130 | return string.replace('$', '$$') 131 | 132 | 133 | def expand(string, vars, local_vars={}): 134 | """Expand a string containing $vars as Ninja would. 135 | Note: doesn't handle the full Ninja variable syntax, but it's enough 136 | to make configure.py's use of it work. 137 | """ 138 | 139 | def exp(m): 140 | var = m.group(1) 141 | if var == '$': 142 | return '$' 143 | return local_vars.get(var, vars.get(var, '')) 144 | 145 | return re.sub(r'\$(\$|\w*)', exp, string) 146 | -------------------------------------------------------------------------------- /src/buildgen/makefile_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import textwrap 4 | from .writer import Writer 5 | 6 | 7 | class MakefileWriter(Writer): 8 | def __init__(self, output, width=78): 9 | super().__init__(output, width) 10 | self.output = output 11 | self.width = width 12 | 13 | def newline(self): 14 | """Add a newline 15 | 16 | """ 17 | self.output.write('\n') 18 | 19 | def comment(self, text): 20 | """ 21 | 22 | :param text: 23 | """ 24 | for line in textwrap.wrap(text, self.width - 2, break_long_words=False, 25 | break_on_hyphens=False): 26 | self.output.write('# ' + line + '\n') 27 | 28 | def variable(self, key, value, indent=0): 29 | """ 30 | 31 | :param key: 32 | :param value: 33 | :param indent: 34 | :return: 35 | """ 36 | if value is None: 37 | return 38 | if isinstance(value, list): 39 | value = ' '.join(filter(None, value)) # Filter out empty strings. 40 | self._line('%s = %s' % (key, value), indent) 41 | 42 | 43 | def pool(self, name, depth): 44 | pass 45 | 46 | def rule(self, name, deps, command): 47 | """ 48 | 49 | :param name: 50 | :param command: 51 | :param description: 52 | :param depfile: 53 | :param generator: 54 | :param pool: 55 | :param restat: 56 | :param rspfile: 57 | :param rspfile_content: 58 | :param deps: 59 | """ 60 | self._line('%s : %s' % (name, deps)) 61 | self.variable('command', command, indent=1) 62 | 63 | def _line(self, text, indent=0): 64 | """Write 'text' word-wrapped at self.width characters.""" 65 | leading_space = ' ' * indent 66 | while len(leading_space) + len(text) > self.width: 67 | # The text is too wide; wrap if possible. 68 | 69 | # Find the rightmost space that would obey our width constraint and 70 | # that's not an escaped space. 71 | available_space = self.width - len(leading_space) - len(' $') 72 | space = available_space 73 | while True: 74 | space = text.rfind(' ', 0, space) 75 | if (space < 0 or 76 | _count_dollars_before_index(text, space) % 2 == 0): 77 | break 78 | 79 | if space < 0: 80 | # No such space; just use the first unescaped space we can find. 81 | space = available_space - 1 82 | while True: 83 | space = text.find(' ', space + 1) 84 | if (space < 0 or 85 | _count_dollars_before_index(text, space) % 2 == 0): 86 | break 87 | if space < 0: 88 | # Give up on breaking. 89 | break 90 | 91 | self.output.write(leading_space + text[0:space] + ' $\n') 92 | text = text[space + 1:] 93 | 94 | # Subsequent lines are continuations, so indent them. 95 | leading_space = ' ' * (indent + 2) 96 | 97 | self.output.write(leading_space + text + '\n') 98 | 99 | def close(self): 100 | self.output.close() 101 | -------------------------------------------------------------------------------- /src/buildgen/ninja_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """This file is from the ninja project itself and is the standard for creating 4 | ninja files in python. 5 | """ 6 | 7 | """Python module for generating .ninja files. 8 | Note that this is emphatically not a required piece of Ninja; it's 9 | just a helpful utility for build-file-generation systems that already 10 | use Python. 11 | """ 12 | 13 | import re, textwrap 14 | from .writer import Writer 15 | 16 | 17 | def escape_path(word): 18 | return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:') 19 | 20 | 21 | def _count_dollars_before_index(s, i): 22 | """Returns the number of '$' characters right in front of s[i].""" 23 | dollar_count = 0 24 | dollar_index = i - 1 25 | while dollar_index > 0 and s[dollar_index] == '$': 26 | dollar_count += 1 27 | dollar_index -= 1 28 | return dollar_count 29 | 30 | 31 | class NinjaWriter(Writer): 32 | def __init__(self, output, width=78): 33 | super().__init__(output, width) 34 | self.output = output 35 | self.width = width 36 | 37 | def newline(self): 38 | """Add a newline 39 | 40 | """ 41 | self.output.write('\n') 42 | 43 | def comment(self, text): 44 | """ 45 | 46 | :param text: 47 | """ 48 | for line in textwrap.wrap(text, self.width - 2, break_long_words=False, 49 | break_on_hyphens=False): 50 | self.output.write('# ' + line + '\n') 51 | 52 | def variable(self, key, value, indent=0): 53 | """ 54 | 55 | :param key: 56 | :param value: 57 | :param indent: 58 | :return: 59 | """ 60 | if value is None: 61 | return 62 | if isinstance(value, list): 63 | value = ' '.join(filter(None, value)) # Filter out empty strings. 64 | self._line('%s = %s' % (key, value), indent) 65 | 66 | def pool(self, name, depth): 67 | """ 68 | 69 | :param name: 70 | :param depth: 71 | """ 72 | self._line('pool %s' % name) 73 | self.variable('depth', depth, indent=1) 74 | 75 | def rule(self, name, command, description=None, depfile=None, 76 | generator=False, pool=None, restat=False, rspfile=None, 77 | rspfile_content=None, deps=None): 78 | """ 79 | 80 | :param name: 81 | :param command: 82 | :param description: 83 | :param depfile: 84 | :param generator: 85 | :param pool: 86 | :param restat: 87 | :param rspfile: 88 | :param rspfile_content: 89 | :param deps: 90 | """ 91 | self._line('rule %s' % name) 92 | self.variable('command', command, indent=1) 93 | if description: 94 | self.variable('description', description, indent=1) 95 | if depfile: 96 | self.variable('depfile', depfile, indent=1) 97 | if generator: 98 | self.variable('generator', '1', indent=1) 99 | if pool: 100 | self.variable('pool', pool, indent=1) 101 | if restat: 102 | self.variable('restat', '1', indent=1) 103 | if rspfile: 104 | self.variable('rspfile', rspfile, indent=1) 105 | if rspfile_content: 106 | self.variable('rspfile_content', rspfile_content, indent=1) 107 | if deps: 108 | self.variable('deps', deps, indent=1) 109 | 110 | def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, 111 | variables=None, implicit_outputs=None, pool=None): 112 | """ 113 | 114 | :param outputs: 115 | :param rule: 116 | :param inputs: 117 | :param implicit: 118 | :param order_only: 119 | :param variables: 120 | :param implicit_outputs: 121 | :param pool: 122 | :return: 123 | """ 124 | outputs = as_list(outputs) 125 | out_outputs = [escape_path(x) for x in outputs] 126 | all_inputs = [escape_path(x) for x in as_list(inputs)] 127 | 128 | if implicit: 129 | implicit = [escape_path(x) for x in as_list(implicit)] 130 | all_inputs.append('|') 131 | all_inputs.extend(implicit) 132 | if order_only: 133 | order_only = [escape_path(x) for x in as_list(order_only)] 134 | all_inputs.append('||') 135 | all_inputs.extend(order_only) 136 | if implicit_outputs: 137 | implicit_outputs = [escape_path(x) 138 | for x in as_list(implicit_outputs)] 139 | out_outputs.append('|') 140 | out_outputs.extend(implicit_outputs) 141 | 142 | self._line('build %s: %s' % (' '.join(out_outputs), 143 | ' '.join([rule] + all_inputs))) 144 | if pool is not None: 145 | self._line(' pool = %s' % pool) 146 | 147 | if variables: 148 | if isinstance(variables, dict): 149 | iterator = iter(variables.items()) 150 | else: 151 | iterator = iter(variables) 152 | 153 | for key, val in iterator: 154 | self.variable(key, val, indent=1) 155 | 156 | return outputs 157 | 158 | def include(self, path): 159 | """ 160 | 161 | :param path: 162 | """ 163 | self._line('include %s' % path) 164 | 165 | def subninja(self, path): 166 | """ 167 | 168 | :param path: 169 | """ 170 | self._line('subninja %s' % path) 171 | 172 | def default(self, paths): 173 | """ 174 | 175 | :param paths: 176 | """ 177 | self._line('default %s' % ' '.join(as_list(paths))) 178 | 179 | def _line(self, text, indent=0): 180 | """Write 'text' word-wrapped at self.width characters.""" 181 | leading_space = ' ' * indent 182 | while len(leading_space) + len(text) > self.width: 183 | # The text is too wide; wrap if possible. 184 | 185 | # Find the rightmost space that would obey our width constraint and 186 | # that's not an escaped space. 187 | available_space = self.width - len(leading_space) - len(' $') 188 | space = available_space 189 | while True: 190 | space = text.rfind(' ', 0, space) 191 | if (space < 0 or 192 | _count_dollars_before_index(text, space) % 2 == 0): 193 | break 194 | 195 | if space < 0: 196 | # No such space; just use the first unescaped space we can find. 197 | space = available_space - 1 198 | while True: 199 | space = text.find(' ', space + 1) 200 | if (space < 0 or 201 | _count_dollars_before_index(text, space) % 2 == 0): 202 | break 203 | if space < 0: 204 | # Give up on breaking. 205 | break 206 | 207 | self.output.write(leading_space + text[0:space] + ' $\n') 208 | text = text[space + 1:] 209 | 210 | # Subsequent lines are continuations, so indent them. 211 | leading_space = ' ' * (indent + 2) 212 | 213 | self.output.write(leading_space + text + '\n') 214 | 215 | def close(self): 216 | self.output.close() 217 | 218 | 219 | def as_list(unknownTypeInput): 220 | """ 221 | 222 | :param unknownTypeInput: 223 | :return: 224 | """ 225 | if unknownTypeInput is None: 226 | return [] 227 | if isinstance(unknownTypeInput, list): 228 | return unknownTypeInput 229 | return [unknownTypeInput] 230 | 231 | 232 | def escape(string): 233 | """Escape a string such that it can be embedded into a Ninja file without 234 | further interpretation.""" 235 | assert '\n' not in string, 'Ninja syntax does not allow newlines' 236 | # We only have one special metacharacter: '$'. 237 | return string.replace('$', '$$') 238 | 239 | 240 | def expand(string, vars, local_vars={}): 241 | """Expand a string containing $vars as Ninja would. 242 | Note: doesn't handle the full Ninja variable syntax, but it's enough 243 | to make configure.py's use of it work. 244 | """ 245 | 246 | def exp(m): 247 | var = m.group(1) 248 | if var == '$': 249 | return '$' 250 | return local_vars.get(var, vars.get(var, '')) 251 | 252 | return re.sub(r'\$(\$|\w*)', exp, string) 253 | -------------------------------------------------------------------------------- /src/buildgen/writer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | class Writer(object): 4 | def __init__(self, output, width=78): 5 | pass 6 | 7 | def newline(self): 8 | pass 9 | 10 | def comment(self, text): 11 | pass 12 | 13 | def variable(self, key, value, indent=0): 14 | pass 15 | 16 | def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, 17 | variables=None, implicit_outputs=None, pool=None): 18 | pass 19 | 20 | def include(self, path): 21 | pass 22 | -------------------------------------------------------------------------------- /src/dragon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/src/dragon/__init__.py -------------------------------------------------------------------------------- /src/dragon/config/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | .▄▄██████████▄▄ 3 | ▄███████▀▀▀▀▀████████▄ 4 | █████▀╙ ¬ └▀▀████▄ 5 | ████▀" ┌¬.█ ▀████▄ 6 | ████" ▄æ████▌▄▄ ▀███▌ ▄▄ 7 | ████ ▄═▄██████████████" ╙███▌ ██ 8 | ▐███─ ▀██▀▀╙╙▄▄██████████▄ ▀███ ▄██▀████ ▐█████ ▄██▀▀██▄ ▄███▀███▌ ▄██▀▀██ ████▀██▌ 9 | ████ ▄██▀▀▀▀.└███████▀½─ ▐███▌ ▐██ ▐██ ▐██ ▄▄▄▐██ ██ ██▌ ▐██ ▐██ ██ ▐██ 10 | ████ ▀█¬ ▐███████ ▐███▌ ▐██ ▐██ ▐██ ██▀ ▐██ ██▄ ██▌ ▐██ ▐██ ██ ██ 11 | ▀███, ;.┌████████─▌ ████ ▀████▀██ ▐██ ▀████▀██ ▀███▀██▌ ▀█████▀ ██ ██ 12 | ████ ▄▀╓██████████ ╫▌▄███▌ ▄▄▄.▄██ 13 | └████, ╓▄██└▄█████████▌▌ █████▀ ▀▀▀▀ version 2.0.0 14 | █████████┌▄███████████═ ▄████▀ ~ cynder 15 | ╙████████████████████▓████▀ 16 | └▀████████████████████▀ this project was made possible by Lorenzo Pane. 17 | ╙▀▀██████████▀▀└ thank you. 18 | -------------------------------------------------------------------------------- /src/dragon/config/defaults.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Special thanks to uroboro for suggesting this 3 | # Configurations here should provide enough default variables to create the specified module; 4 | # Please reach out to cynder if you need advice on extending these 5 | Defaults: 6 | dragon_data_dir: .dragon 7 | builddir: $dragon_data_dir/build 8 | objdir: $dragon_data_dir/obj 9 | signdir: $dragon_data_dir/sign 10 | bridging-header: $name-Bridging-Header.h 11 | dragon_root_dir: $$DRAGON_ROOT_DIR 12 | toolchain-prefix: '' 13 | tool-prefix: '' 14 | nopack: false 15 | logos: $dragon_root_dir/src/logos/bin/logos.pl 16 | cargo: cargo 17 | cargo-nightly: 'cargo +nightly' 18 | optool: optool 19 | stage: 20 | - 'true' 21 | wild_recurse: false 22 | warnings: -Wall 23 | optim: "0" 24 | debug: -fcolor-diagnostics 25 | idflag: '' 26 | entflag: '-S' 27 | entfile: '' 28 | resource_dir: 'Resources' 29 | fw_dirs: 30 | - $dragon_root_dir/sdks/iPhoneOS.sdk/System/Library/PrivateFrameworks/ 31 | - $dragon_root_dir/frameworks 32 | lib_dirs: 33 | - $dragon_root_dir/lib 34 | - '.' 35 | additional_lib_dirs: 36 | additional_fw_dirs: 37 | prefix: [] 38 | cinclude: '-I$dragon_root_dir/include -I$dragon_root_dir/include/_fallback -I$dragon_root_dir/headers/ -I$pwd' 39 | stagedir: '_' 40 | modulesinternal: '-fmodules -fcxx-modules -fmodule-name=$name -fbuild-session-file=$dragon_data_dir/modules/ -fmodules-validate-once-per-build-session -fmodules-prune-after=345600 -fmodules-prune-interval=86400' 41 | 42 | # Touching these without a firm grasp of what you're doing is extremely likely to break things 43 | # And assigning to them within a DragonMake will outdate that project *any* time these are updated 44 | # They serve to abstract how we slap together all of the clang args 45 | # Seriously dont touch em. 46 | InternalDefaults: 47 | internaldbgflags: '-DDEBUG' 48 | internalreleaseflags: '-DNDEBUG' 49 | internalcflags: '$cinclude $debug $fwSearch $cflags $btarg -O$optim $sysroot $header_includes $arc $triple $theosshim $macros $prefix $warnings $modulesinternal $internaldbgflags $internalreleaseflags $dbgflags $releaseflags' 50 | internalswiftflags: '-color-diagnostics -enable-objc-interop -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos -g -L/usr/lib/swift -swift-version 5 -module-name $name' 51 | internalfflags: '$internalcflags $typeldflags $frameworks $libs $libflags $lopts $libSearch $ldflags' 52 | internalldflags: '$internalcflags $typeldflags $frameworks $libs $libflags $lopts $libSearch $ldflags' 53 | internalsigntarget: '$signdir/$build_target_file.unsigned' 54 | internalsymtarget: '$signdir/$build_target_file.unsym' 55 | pwd: '.' 56 | 57 | # Applied on top of both of these. 58 | Rootless: 59 | rootless_prefix: '/var/jb' 60 | rpathflags: '-rpath $rootless_prefix/Library/Frameworks -rpath $rootless_prefix/usr/lib' 61 | internalldflags: '$internalcflags $typeldflags $frameworks $libs $libflags $lopts $libSearch $ldflags $rpathflags' 62 | -------------------------------------------------------------------------------- /src/dragon/config/rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | logos: 3 | name: "Logos Preprocessor" 4 | desc: "Preprocessing $in using Logos" 5 | cmd: "$logos $in > $out" 6 | 7 | 8 | swift: 9 | name: "Swift {arch}" 10 | desc: "Compiling $in with Swift [{arch}]" 11 | cmd: "SwiftFiles=$$(echo '$swiftfiles $in' | tr ' ' '\\n' | sort | uniq -u); $swift -frontend -c $internalswiftflags $bridgeheader -target {arch}-apple-ios -emit-module-path $out.swiftmodule -primary-file $in $$SwiftFiles -o $out" 12 | swiftmoduleheader: 13 | name: "Swift Module Header" 14 | desc: "Generating module header for $in with Swift [{arch}]" 15 | cmd: "$swift -frontend -c $internalswiftflags $bridgeheader -target arm64-apple-ios8.0 -emit-module -merge-modules $in -emit-objc-header-path $out -o /dev/null" 16 | 17 | 18 | cargo: 19 | name: "cargo {arch}" 20 | desc: "Compiling $in with $cc [{arch}]" 21 | cmd: "$cargo-nightly --target={arch}-apple-darwin10 $internalcflags -c $in -o $out" 22 | 23 | c: 24 | name: "clang {arch}" 25 | desc: "Compiling $in with $cc [{arch}]" 26 | cmd: "$cc -arch {arch} $internalcflags -c $in -o $out" 27 | 28 | cxx: 29 | name: "clang++ {arch}" 30 | desc: "Compiling $in with $cxx [{arch}]" 31 | cmd: "$cxx -arch {arch} $internalcflags -c $in -o $out" 32 | 33 | objc: 34 | name: "clang {arch}" 35 | desc: "Compiling $in with $cc [{arch}]" 36 | cmd: "$cc -arch {arch} $internalcflags -c $in -o $out" 37 | 38 | objcxx: 39 | name: "clang++ {arch}" 40 | desc: "Compiling $in with $cxx [{arch}]" 41 | cmd: "$cxx -arch {arch} $internalcflags -c $in -o $out" 42 | 43 | 44 | link: 45 | name: "{arch} Linker" 46 | desc: "Linking $in with $ld [{arch}]" 47 | cmd: "$ld -arch {arch} $internallflags $internalldflags -o $out $in" 48 | archive: 49 | name: "Creating Archive" 50 | desc: "Creating Static Archive with $ar from $in" 51 | cmd: "ar cr $out $in" 52 | dummy: 53 | name: "Copying Files" 54 | desc: "Copying Files" 55 | cmd: "cp $in $out" 56 | copy: 57 | name: "Copying Files" 58 | desc: "Copying Files" 59 | cmd: "cp $in $out" 60 | lipo: 61 | name: "Lipo Utility" 62 | desc: "Merging architechtures" 63 | cmd: "$lipo -create $in -output $out" 64 | bundle: 65 | name: "Bundle Resources" 66 | desc: "Copying Bundle Resources" 67 | cmd: "mkdir -p $dragon_data_dir/_$location/ && cp -R $resource_dir/ $dragon_data_dir/_$location" 68 | debug: 69 | name: "DsymUtil" 70 | desc: "Generating Debug Symbols for $name" 71 | cmd: "cp $in $out" 72 | sign: 73 | name: "$codesign" 74 | desc: "Signing $name" 75 | cmd: "$codesign $entflag$entfile $in && cp $in $out" 76 | stage: 77 | name: "Staging Commands" 78 | desc: "Running Stage for $name" 79 | cmd: "$stage && $stage2" 80 | -------------------------------------------------------------------------------- /src/dragon/config/state.yml: -------------------------------------------------------------------------------- 1 | --- 2 | device: 3 | current: 0 4 | devices: 5 | - ip: localhost 6 | port: 4444 7 | -------------------------------------------------------------------------------- /src/dragon/config/targets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Targets: 3 | sim: 4 | all: 5 | targetos: iphonesimulator 6 | triple: MACHINE 7 | arc: true 8 | fw_dirs: 9 | - /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 10 | - $dragon_root_dir/frameworks 11 | sysroot: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 12 | archs: 13 | - MACHINE 14 | ios: 15 | all: 16 | targetos: iphoneos 17 | targetvers: 9.0 18 | triple: arm64-apple-ios$targetvers 19 | arc: true 20 | sysroot: $dragon_root_dir/sdks/iPhoneOS.sdk 21 | archs: 22 | - arm64 23 | - arm64e 24 | watchos: 25 | all: 26 | targetos: watchos 27 | targetvers: 5.0 28 | arc: true 29 | sysroot: $dragon_root_dir/sdks/WatchOS.sdk 30 | archs: arm64_32 31 | host: 32 | all: 33 | triple: MACHINE 34 | archs: 35 | - MACHINE 36 | -------------------------------------------------------------------------------- /src/dragon/config/tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ProjectTests: 3 | General: 4 | - https://github.com/kritanta-ios-tweaks/Chapters 5 | - https://github.com/kritanta-ios-tweaks/Shakelight 6 | - https://github.com/kritanta-ios-tweaks/Gravitation 7 | - https://github.com/cxnder/Docky 8 | Globbing: 9 | - https://github.com/kritanta-ios-tweaks/Signe 10 | Legacy: 11 | - https://github.com/kritanta-ios-tweaks/StatusViz 12 | Theos: 13 | - https://github.com/kritanta-ios-tweaks/DeadRinger 14 | - https://github.com/cxnder/libCozy 15 | BaselineData: 16 | # TODO: Update this baseline 17 | cpu_bench: 1.3837896153333336 18 | ProjectScores: 19 | https://github.com/kritanta-ios-tweaks/Chapters: 0.7295811089999997 20 | https://github.com/kritanta-ios-tweaks/Shakelight: 0.7603968410000022 21 | https://github.com/kritanta-ios-tweaks/StatusViz: 1.176904830999998 22 | https://github.com/cxnder/Docky: 1.3256995099999997 23 | https://github.com/cxnder/libCozy: 0.7155977580000004 24 | -------------------------------------------------------------------------------- /src/dragon/config/types.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Types: 3 | comprehensive: 4 | app: 5 | variables: 6 | install_location: '/Applications' 7 | build_target_file: '$dragon_data_dir/$stagedir$location/$name.app/$name' 8 | stage2: 9 | - 'cp -R $resource_dir/* $dragon_data_dir/$stagedir$location/$name.app/' 10 | application: 11 | variables: 12 | install_location: '/Applications' 13 | build_target_file: '$dragon_data_dir/$stagedir$location/$name.app/$name' 14 | stage2: 15 | - 'cp -R $resource_dir/* $dragon_data_dir/$stagedir$location/$name.app/' 16 | tweak-jailed: 17 | variables: 18 | install_location: '/Applications' 19 | build_target_file: '$dragon_data_dir/$stagedir$location/$name.app/$name' 20 | embed-libs: 21 | - CydiaSubstrate 22 | stage2: 23 | - 'cp -R $resource_dir/* $dragon_data_dir/$stagedir$location/$name.app/' 24 | tweak: 25 | variables: 26 | install_location: '/Library/MobileSubstrate/DynamicLibraries' 27 | build_target_file: '$dragon_data_dir/$stagedir$location/$name.dylib' 28 | lopts: '-dynamiclib -ggdb -framework CydiaSubstrate' 29 | ldflags: '-install_name @rpath/$name.dylib' 30 | frameworks: 31 | - UIKit 32 | stage2: 33 | - 'cp $name.plist $dragon_data_dir/$stagedir$location/$name.plist 2>/dev/null || python3 -m dragongen.bfilter $dragon_data_dir/DragonMake $name > $dragon_data_dir/$stagedir$location/$name.plist' 34 | prefs: 35 | variables: 36 | install_location: '/Library/PreferenceBundles' 37 | build_target_file: '$dragon_data_dir/$stagedir$location/$name.bundle/$name' 38 | libs: 39 | - 'substrate' 40 | lopts: '-dynamiclib -ggdb -framework Preferences' 41 | ldflags: '-install_name $location/$name.bundle/$name' 42 | frameworks: 43 | - UIKit 44 | stage2: 45 | - 'mkdir -p $dragon_data_dir/$stagedir/Library/PreferenceLoader/Preferences/' 46 | - 'cp entry.plist $dragon_data_dir/stagedir/Library/PreferenceLoader/Preferences/$name.plist 2> /dev/null' 47 | - 'cp -R $resource_dir/* $dragon_data_dir/$stagedir$location/$name.bundle' 48 | bundle: 49 | variables: 50 | install_location: '/Library' 51 | build_target_file: '$dragon_data_dir/$stagedir$location/$name.bundle/$name' 52 | lopts: '-dynamiclib -ggdb' 53 | ldflags: '-install_name $location/$name.bundle/$name' 54 | frameworks: 55 | - UIKit 56 | stage2: 57 | - 'cp -R $resource_dir/* $dragon_data_dir/$stagedir$location/$name.bundle/' 58 | resource-bundle: 59 | variables: 60 | install_location: '/Library/$name/$name.bundle/' 61 | build_target_file: 'build.ninja' 62 | stage2: 63 | - 'true;' 64 | framework: 65 | variables: 66 | install_location: '/Library/Frameworks' 67 | build_target_file: '$dragon_data_dir/$stagedir$location/$name.framework/$name' 68 | public_headers: '$dragon_root_dir/include/' 69 | lopts: '-dynamiclib -ggdb' 70 | ldflags: '-install_name @rpath/$name.framework/$name' 71 | frameworks: 72 | - Foundation 73 | stage2: 74 | - 'cp -R $resource_dir/* $dragon_data_dir/$stagedir$location/$name.framework' 75 | - 'cp -R $dragon_data_dir/$stagedir$location/$name.framework $dragon_root_dir/frameworks/$name.framework' 76 | - 'if ! [ -z "$public_headers" ]; then 77 | mkdir -p $dragon_data_dir/$stagedir$location/$name.framework/Headers; 78 | cp $public_headers $dragon_data_dir/$stagedir$location/$name.framework/Headers; 79 | fi' 80 | cli: 81 | variables: 82 | install_location: '/usr/local/bin' 83 | build_target_file: '$dragon_data_dir/$stagedir$location/$name' 84 | stage2: 85 | - 'true;' 86 | binary: 87 | variables: 88 | install_location: '/usr/local/bin' 89 | build_target_file: '$dragon_data_dir/$stagedir$location/$name' 90 | stage2: 91 | - 'true;' 92 | tool: 93 | variables: 94 | install_location: '/usr/local/bin' 95 | build_target_file: '$dragon_data_dir/$stagedir$location/$name' 96 | stage2: 97 | - 'true;' 98 | library: 99 | variables: 100 | install_location: '/usr/local/lib' 101 | build_target_file: '$dragon_data_dir/$stagedir$location/$name.dylib' 102 | ldflags: '-install_name @rpath/$name.dylib' 103 | lopts: '-dynamiclib -ggdb' 104 | stage2: 105 | - 'cp $build_target_file $dragon_root_dir/lib/' 106 | static: 107 | variables: 108 | install_location: '/usr/local/lib' 109 | build_target_file: '$dragon_data_dir/$stagedir$location/$name.a' 110 | stage2: 111 | - 'cp $build_target_file $dragon_root_dir/lib/' 112 | stage: 113 | variables: 114 | build_target_file: 'build.ninja' 115 | stage2: 116 | - 'true;' 117 | raw: 118 | -------------------------------------------------------------------------------- /src/dragon/device.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | ''' 4 | 5 | device.py 6 | 7 | (c) 2020 cynder 8 | Please refer to the LICENSE file included with dragon regarding the usage of code herein. 9 | 10 | https://dragon.cynder.me/ 11 | https://github.com/DragonBuild/dragon 12 | 13 | ''' 14 | 15 | import os, sys, yaml, socket 16 | from shared.util import dbstate, dbwarn, dberror 17 | from shared.util import system, system_with_output, system_pipe_output 18 | 19 | class DeviceShell: 20 | @staticmethod 21 | def launch(device): 22 | current_directory = "~" 23 | while True: 24 | command = input(f'{current_directory} > ') 25 | if command.startswith('cd'): 26 | arg = command.split(' ')[-1] 27 | if arg.startswith('/'): 28 | current_directory = arg 29 | else: 30 | current_directory += "/" + arg 31 | system_pipe_output(f'ssh -p {device.port} root@{device.host} "cd {current_directory}; {command}"') 32 | 33 | 34 | class Device: 35 | def __init__(self, host: str, port: int, timeout: int = 5): 36 | self.host = host 37 | self.port = port 38 | self.timeout = timeout 39 | 40 | def as_dict(self): 41 | return {'ip': self.host, 'port': self.port} 42 | 43 | def test_connection(self): 44 | try: 45 | check = lambda x, y, z: (lambda s: (s.settimeout(z), s.connect((x, int(y))), s.close(), True))( 46 | socket.socket(socket.AF_INET, socket.SOCK_STREAM)) 47 | check(socket.gethostbyname(self.host), self.port, self.timeout) 48 | except: 49 | return False 50 | return True 51 | 52 | def connection_failure_resolver(self): 53 | status, stdout, stderr = system_with_output(f'ssh -p {self.port} root@{self.host} "true"') 54 | print(f'Error Message:\n{stderr}') 55 | 56 | def test_keybased_auth(self): 57 | return system(f'ssh -o PasswordAuthentication=no -p {self.port} root@{self.host} 2>/dev/null "true"') == 0 58 | 59 | def check_known_hosts_issue(self): 60 | status, stdout, stderr = system_with_output(f'ssh -p {self.port} root@{self.host} "true"') 61 | if status == 255 and stderr != "" and "known_hosts" in stderr: 62 | # sick, there's a bad entry in known hosts 63 | return False, stderr 64 | return True, "" 65 | 66 | def run_cmd(self, cmd, quiet=False): 67 | if cmd == "none": 68 | return 69 | 70 | if not self.test_connection(): 71 | dberror("Device", f'Could not connect to device at {self.host}:{self.port}') 72 | if self.host == "localhost" and self.port == 4444: 73 | dberror("Device", 'To configure a new device, run "dragon s"') 74 | self.connection_failure_resolver() 75 | return 76 | 77 | if not quiet: 78 | if cmd == '': 79 | dbstate("Device", "Launching Device Shell") 80 | DeviceShell.launch(self) 81 | return 82 | dbstate("Device", f'Running "{cmd}" on {self.host}:{self.port}') 83 | return system_pipe_output(f'ssh -p {self.port} root@{self.host} "{cmd}"') 84 | 85 | def export_ip(self): 86 | exports = { 87 | 'DRBIP': self.host, 88 | 'DRBPORT': self.port 89 | } 90 | for x in exports: 91 | print(f'export {x}="{exports[x]}"') 92 | 93 | def setup_key_auth(self): 94 | dbstate("Device", 'Setting up keybased auth') 95 | exists = system('stat ~/.ssh/id_rsa') == 0 96 | if not exists: 97 | dbstate("Device", 'Generating Keyfile') 98 | system("ssh-keygen -q -t rsa -N '' -f ~/.ssh/id_rsa <<&1 >/dev/null") 99 | 100 | # We don't use ssh-copy-id because some systems (Elucubratus, etc) don't have it 101 | dbstate("Device", 'Copying keyfile') 102 | success = system( 103 | f'cat ~/.ssh/id_rsa.pub | ssh -p {self.port} root@{self.host} "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"') 104 | if success == 0: 105 | dbstate("Device", 'Enabled keybased auth') 106 | else: 107 | dberror("Device", 'Failed') 108 | 109 | 110 | class DeviceManager(object): 111 | 112 | def __init__(self): 113 | with open(f'{os.environ["DRAGON_ROOT_DIR"]}/internal/state.yml') as state: 114 | dragon_state = yaml.safe_load(state) 115 | 116 | self.dragon_state = dragon_state 117 | devices: list = dragon_state['device']['devices'] 118 | self.devices = [] 119 | for i in devices: 120 | self.devices.append(Device(i['ip'], i['port'])) 121 | 122 | self.current = self.devices[dragon_state['device']['current']] 123 | 124 | def savestate(self): 125 | with open(f'{os.environ["DRAGON_ROOT_DIR"]}/internal/state.yml', 'w') as state: 126 | yaml.dump(self.dragon_state, state) 127 | 128 | def add_device(self, device: Device): 129 | self.dragon_state['device']['devices'].append({'ip': device.as_dict()['ip'], 'port': device.as_dict()['port']}) 130 | self.devices.append(device) 131 | self.dragon_state['device']['current'] = len(self.devices) - 1 132 | self.savestate() 133 | 134 | # noinspection PyMethodMayBeStatic 135 | def resolve_known_hosts_issue(self, stderr): 136 | known_hosts_line = "" 137 | file_location = "" 138 | for line in stderr.split("\n"): 139 | if 'Offending' in line: 140 | known_hosts_line = int(line.split('known_hosts:')[-1]) 141 | file_location = line.split(' key in ')[-1].split(":")[0] 142 | 143 | dbwarn("Device", "There is already an entry in the known_hosts file for your system") 144 | if file_location: 145 | dbwarn("Device", f'Bad entry: {file_location}:{known_hosts_line}') 146 | dbwarn("Device", "You will not be able to connect to this device until this is resolved. Remove this line? (y/n)") 147 | if 'y' in input('> ').lower(): 148 | try: 149 | with open(file_location, "r") as infile: 150 | lines = infile.readlines() 151 | 152 | with open(file_location, "w") as outfile: 153 | for pos, line in enumerate(lines): 154 | pos += 1 155 | if pos != known_hosts_line: 156 | outfile.write(line) 157 | else: 158 | print(f'Removed {line}') 159 | return True 160 | except IOError: 161 | dberror("Device", "Error reading or writing to file. Please manually remove the line.") 162 | return False 163 | except Exception: 164 | dberror("Device", "Unknown error occured.") 165 | return False 166 | 167 | dberror("Device", "Could not automatically resolve any information about the error. Please See the following output.") 168 | print(stderr) 169 | return False 170 | 171 | def setup(self): 172 | ''' 173 | Setup checklist: 174 | 1. Get IP/Port, create a device 175 | 2. Test connection 176 | 3. Check and setup key auth 177 | 178 | ''' 179 | 180 | dbstate("Device", 'Enter Device IP or hostname') 181 | ip = input('>>> ') 182 | dbstate("Device", 'enter port (leave empty for 22)') 183 | port = input('>>> ') 184 | 185 | if port == '': 186 | port = 22 187 | 188 | port = int(port) 189 | 190 | device = Device(ip, port) 191 | 192 | dbstate("Device", 'Testing Connection') 193 | connected = False 194 | if device.test_connection(): 195 | dbstate("Device", 'Connected!') 196 | connected = True 197 | else: 198 | dbwarn("Device", 'Connection failed, add it anyways? (y/n)') 199 | if 'y' not in input('> ').lower(): 200 | return 201 | 202 | if connected: 203 | success, stderr = device.check_known_hosts_issue() 204 | if not success: 205 | resolved = True 206 | while resolved: 207 | resolved = self.resolve_known_hosts_issue(stderr) 208 | if resolved: 209 | success, stderr = device.check_known_hosts_issue() 210 | if success: 211 | break 212 | if success: 213 | dbstate("Device", "Successfully resolved issue") 214 | else: 215 | dberror("Device", "Could not resolve known_hosts issue.") 216 | 217 | if not device.test_keybased_auth(): 218 | device.setup_key_auth() 219 | else: 220 | dbstate("Device", "Keybased auth already configured") 221 | 222 | self.add_device(device) 223 | 224 | 225 | # device.py cmd 226 | def main(): 227 | device_manager = DeviceManager() 228 | if 'setup' in sys.argv[1]: 229 | try: 230 | device_manager.setup() 231 | except KeyboardInterrupt: 232 | print() 233 | dbstate("Device", 'Cancelled') 234 | if 'run' in sys.argv[1]: 235 | device_manager.current.run_cmd(' '.join(sys.argv[2:])) 236 | if 'qr' in sys.argv[1]: 237 | device_manager.current.run_cmd(' '.join(sys.argv[2:]), quiet=True) 238 | if 'get' in sys.argv[1]: 239 | device_manager.current.export_ip() 240 | if 'test' in sys.argv[1]: 241 | dbstate("Device", 'Testing Connection') 242 | if device_manager.current.test_connection(): 243 | dbstate("Device", 'Connected!') 244 | exit(0) 245 | else: 246 | dberror("Device", 'Connection to device failed') 247 | dberror("Device", 'Make sure SSH is functioning properly and/or run "dragon s" to configure your device') 248 | exit(1) 249 | 250 | 251 | if __name__ == '__main__': 252 | main() 253 | -------------------------------------------------------------------------------- /src/dragon/editor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # this file kind of sucks because i didn't really want to write it, honestly 4 | # you (end user) are far better off learning to use the yaml format, its fairly simple to lay stuff 5 | # out yourself. 6 | # but this also exists and should work good. 7 | 8 | # if you're here to contribute to this file, im sorry. ::::) 9 | 10 | import os, pwd 11 | import ruyaml as yaml 12 | from shared.util import dbstate 13 | 14 | 15 | def get_input(prompt, default): 16 | dbstate("Project Editor", f'{prompt} ({default})') 17 | ret = input('>> ') 18 | return ret if ret.strip() else default 19 | 20 | 21 | def get_from_selector(prompt, values, default): 22 | dbstate("Project Editor", prompt) 23 | itemlist = [] 24 | for i, key in enumerate(values): 25 | print(f'[{i}] > {key}') 26 | itemlist.append(values[key]) 27 | item = int(get_input('Select Item', default)) 28 | return itemlist[item] 29 | 30 | 31 | AppDelegate_h = """#import 32 | 33 | @interface AppDelegate : UIResponder 34 | @property (nonatomic, strong) UIWindow *window; 35 | @property (nonatomic, strong) UINavigationController *rootViewController; 36 | @end 37 | """ 38 | AppDelegate_m = """#import "AppDelegate.h" 39 | #import "RootViewController.h" 40 | 41 | @implementation AppDelegate 42 | 43 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 44 | _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 45 | _rootViewController = [[UINavigationController alloc] initWithRootViewController:[[RootViewController alloc] init]]; 46 | _window.rootViewController = _rootViewController; 47 | [_window makeKeyAndVisible]; 48 | return YES; 49 | } 50 | 51 | @end 52 | """ 53 | RootViewController_h = """#import 54 | 55 | @interface RootViewController : UITableViewController 56 | @end 57 | """ 58 | RootViewController_m = """#import "RootViewController.h" 59 | 60 | @interface RootViewController () 61 | @property (nonatomic, strong) NSMutableArray * objects; 62 | @end 63 | 64 | @implementation RootViewController 65 | 66 | - (void)loadView { 67 | [super loadView]; 68 | 69 | _objects = [NSMutableArray array]; 70 | 71 | self.title = @"Root View Controller"; 72 | self.navigationItem.leftBarButtonItem = self.editButtonItem; 73 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addButtonTapped:)]; 74 | } 75 | 76 | - (void)addButtonTapped:(id)sender { 77 | [_objects insertObject:[NSDate date] atIndex:0]; 78 | [self.tableView insertRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:0 inSection:0] ] withRowAnimation:UITableViewRowAnimationAutomatic]; 79 | } 80 | 81 | #pragma mark - Table View Data Source 82 | 83 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 84 | return 1; 85 | } 86 | 87 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 88 | return _objects.count; 89 | } 90 | 91 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 92 | static NSString *CellIdentifier = @"Cell"; 93 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 94 | 95 | if (!cell) { 96 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; 97 | } 98 | 99 | NSDate *date = _objects[indexPath.row]; 100 | cell.textLabel.text = date.description; 101 | return cell; 102 | } 103 | 104 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 105 | [_objects removeObjectAtIndex:indexPath.row]; 106 | [tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic]; 107 | } 108 | 109 | #pragma mark - Table View Delegate 110 | 111 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 112 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 113 | } 114 | 115 | @end 116 | """ 117 | main_m = """#import 118 | #import "AppDelegate.h" 119 | 120 | int main(int argc, char *argv[]) { 121 | @autoreleasepool { 122 | return UIApplicationMain(argc, argv, nil, NSStringFromClass(AppDelegate.class)); 123 | } 124 | } 125 | """ 126 | 127 | InfoPlist = """ 128 | 129 | 130 | 131 | CFBundleExecutable 132 | bluh 133 | CFBundleIcons 134 | 135 | CFBundlePrimaryIcon 136 | 137 | CFBundleIconFiles 138 | 139 | AppIcon29x29 140 | AppIcon40x40 141 | AppIcon57x57 142 | AppIcon60x60 143 | 144 | UIPrerenderedIcon 145 | 146 | 147 | 148 | CFBundleIcons~ipad 149 | 150 | CFBundlePrimaryIcon 151 | 152 | CFBundleIconFiles 153 | 154 | AppIcon29x29 155 | AppIcon40x40 156 | AppIcon57x57 157 | AppIcon60x60 158 | AppIcon50x50 159 | AppIcon72x72 160 | AppIcon76x76 161 | 162 | UIPrerenderedIcon 163 | 164 | 165 | 166 | CFBundleIdentifier 167 | {} 168 | CFBundleInfoDictionaryVersion 169 | 6.0 170 | CFBundlePackageType 171 | APPL 172 | CFBundleSignature 173 | ???? 174 | CFBundleSupportedPlatforms 175 | 176 | iPhoneOS 177 | 178 | CFBundleVersion 179 | 1.0 180 | LSRequiresIPhoneOS 181 | 182 | UIDeviceFamily 183 | 184 | 1 185 | 2 186 | 187 | UIRequiredDeviceCapabilities 188 | 189 | armv7 190 | 191 | UILaunchImageFile 192 | LaunchImage 193 | UISupportedInterfaceOrientations 194 | 195 | UIInterfaceOrientationPortrait 196 | UIInterfaceOrientationLandscapeLeft 197 | UIInterfaceOrientationLandscapeRight 198 | 199 | UISupportedInterfaceOrientations~ipad 200 | 201 | UIInterfaceOrientationPortrait 202 | UIInterfaceOrientationPortraitUpsideDown 203 | UIInterfaceOrientationLandscapeLeft 204 | UIInterfaceOrientationLandscapeRight 205 | 206 | 207 | 208 | """ 209 | 210 | Prefs_LPLP_NamePlist = """ 211 | 212 | 213 | 214 | entry 215 | 216 | bundle 217 | {} 218 | cell 219 | PSLinkCell 220 | detail 221 | {}RootListController 222 | icon 223 | icon.png 224 | isController 225 | 226 | label 227 | {} 228 | 229 | 230 | """ 231 | 232 | Prefs_R_InfoPlist = """ 233 | 234 | 235 | 236 | CFBundleDevelopmentRegion 237 | English 238 | CFBundleExecutable 239 | {} 240 | CFBundleIdentifier 241 | {} 242 | CFBundleInfoDictionaryVersion 243 | 6.0 244 | CFBundlePackageType 245 | BNDL 246 | CFBundleShortVersionString 247 | 1.0.0 248 | CFBundleSignature 249 | ???? 250 | CFBundleVersion 251 | 1.0 252 | NSPrincipalClass 253 | {}RootListController 254 | 255 | 256 | """ 257 | 258 | Prefs_R_RootPlist = """ 259 | 260 | 261 | 262 | items 263 | 264 | 265 | cell 266 | PSGroupCell 267 | label 268 | {} First Page 269 | 270 | 271 | cell 272 | PSSwitchCell 273 | default 274 | 275 | defaults 276 | {} 277 | key 278 | AwesomeSwitch1 279 | label 280 | Awesome Switch 1 281 | 282 | 283 | title 284 | {} 285 | 286 | 287 | """ 288 | 289 | Prefs_RootListController_h = """#import 290 | 291 | @interface {}RootListController : PSListController 292 | @end 293 | """ 294 | Prefs_RootListController_m = """#import 295 | #import "{}RootListController.h" 296 | 297 | @implementation {}RootListController 298 | 299 | - (NSArray *)specifiers {{ 300 | if (!_specifiers) {{ 301 | _specifiers = [self loadSpecifiersFromPlistName:@"Root" target:self]; 302 | }} 303 | 304 | return _specifiers; 305 | }} 306 | 307 | @end 308 | """ 309 | 310 | 311 | FWK_R_InfoPlist = """ 312 | 313 | 314 | 315 | CFBundleDevelopmentRegion 316 | English 317 | CFBundleExecutable 318 | {} 319 | CFBundleIdentifier 320 | {} 321 | CFBundleInfoDictionaryVersion 322 | 6.0 323 | CFBundlePackageType 324 | FMWK 325 | CFBundleShortVersionString 326 | 1.0 327 | CFBundleSignature 328 | ???? 329 | CFBundleVersion 330 | 1 331 | 332 | 333 | """ 334 | 335 | FWK_name_h = """// Umbrella header for {}. 336 | // Add import lines for each public header, like this: #import <{}/XXXAwesomeClass.h> 337 | // Don’t forget to also add them to {} in your Makefile! 338 | """ 339 | 340 | CLI_ents_plist = """ 341 | 342 | 343 | platform-application 344 | 345 | com.apple.private.security.container-required 346 | 347 | 348 | 349 | """ 350 | 351 | class Project: 352 | def __init__(self, root_directory): 353 | self.root_directory = root_directory 354 | self.directory_name = self.root_directory.split('/')[-1] 355 | self.current_username = pwd.getpwuid(os.getuid()).pw_name 356 | self.variables = {} 357 | 358 | def create_new(self): 359 | dbstate("Project Editor", '-=-=============================') 360 | dbstate("Project Editor", 'Creating new Package') 361 | dbstate("Project Editor", '-=-=============================') 362 | self.variables['name'] = get_input('Project Name', self.directory_name) 363 | self.variables['id'] = get_input('Bundle ID', f'com.{self.current_username}.{self.directory_name}') 364 | self.variables['depends'] = 'mobilesubstrate' 365 | self.variables['architecture'] = 'iphoneos-arm' 366 | self.variables['version'] = get_input('Version', '0.0.1') 367 | self.variables['description'] = get_input('Description', 'A cool MobileSubstrate Tweak') 368 | self.variables['author'] = get_input('Author', self.current_username) 369 | self.variables['section'] = 'Tweaks' 370 | 371 | 372 | class Module: 373 | def __init__(self, variables=None): 374 | if variables is None: 375 | variables = {} 376 | self.variables = {} 377 | if 'id' in variables: 378 | self.variables['id'] = variables['id'] 379 | if 'author' in variables: 380 | self.variables['author'] = variables['author'] 381 | self.name = '' 382 | 383 | def create_new(self, proj_root): 384 | dbstate("Project Editor", '-=-=============================') 385 | dbstate("Project Editor", 'Creating new Module') 386 | dbstate("Project Editor", '-=-=============================') 387 | self.variables['type'] = get_from_selector('Select Module Type', {'Tweak': 'tweak', 'App': 'app', 388 | 'CLI Tool': 'cli', 'Library': 'library', 389 | 'Preference Bundle': 'prefs', 390 | 'Framework': 'framework',}, '0') 391 | self.name = get_input('Name', 'ModuleName') 392 | while True: 393 | subdir = get_input('Subdirectory Name (Leave empty to work in current directory)', '') 394 | if subdir != '': 395 | if os.path.exists(subdir): 396 | print('File/Directory already exists;') 397 | continue 398 | else: 399 | self.variables['dir'] = subdir 400 | os.mkdir(subdir) 401 | break 402 | else: 403 | break 404 | self._new_for_type(self.variables['type'], proj_root) 405 | 406 | def _new_for_type(self, type, proj_root): 407 | if 'dir' in self.variables: 408 | os.chdir(self.variables['dir']) 409 | 410 | if type == 'tweak': 411 | self.variables['filter'] = { 412 | 'executables': get_input('Comma separated list of processes to target', 'SpringBoard').split(', ')} 413 | self.variables['files'] = [f'{self.name}.x'] 414 | with open(f'{self.name}.x', 'w') as out: 415 | out.write('// Insert your code here!\n') 416 | elif type == 'cli': 417 | self.variables['entfile'] = 'ents.plist' 418 | with open('ents.plist', 'w') as out: 419 | out.write(CLI_ents_plist) 420 | self.variables['files'] = [f'{self.name}.m'] 421 | with open(f'{self.name}.m', 'w') as out: 422 | out.write('// Insert your code here!\n') 423 | elif type == 'framework': 424 | if not os.path.exists('Resources'): 425 | os.mkdir('Resources') 426 | with open('Resources/Info.plist', 'w') as out: 427 | out.write(FWK_R_InfoPlist.format(self.name, self.variables['id'])) 428 | with open(f'{self.name}.h', 'w') as out: 429 | out.write(FWK_name_h.format(self.name, self.name, self.name)) 430 | with open(f'{self.name}.m', 'wb') as out: 431 | out.write(b'') 432 | self.variables['files'] = [f'{self.name}.m'] 433 | elif type == 'library': 434 | with open(f'{self.name}.m', 'wb') as out: 435 | out.write(b'') 436 | self.variables['files'] = [f'{self.name}.m'] 437 | elif type == 'prefs': 438 | self.variables['prefix'] = get_input('Class name prefix (three or more characters unique to this project)', 439 | self.name) 440 | layoutPath = os.path.join(proj_root, 'layout', 'Library', 'PreferenceLoader', 'Preferences') 441 | if not os.path.exists(layoutPath): 442 | os.makedirs(layoutPath, exist_ok=True) 443 | with open(os.path.join(layoutPath, self.name + '.plist'), 'w') as out: 444 | out.write(Prefs_LPLP_NamePlist.format(self.name, self.variables['prefix'], self.name)) 445 | if not os.path.exists('Resources'): 446 | os.mkdir('Resources') 447 | for f in ['icon.png', 'icon@2x.png', 'icon@3x.png']: 448 | with open(os.path.join('Resources', f), 'wb') as out: 449 | out.write(b'') 450 | with open('Resources/Info.plist', 'w') as out: 451 | out.write(Prefs_R_InfoPlist.format(self.name, self.variables['id'], self.variables['prefix'])) 452 | with open('Resources/Root.plist', 'w') as out: 453 | out.write(Prefs_R_RootPlist.format(self.name, self.variables['id'], self.name)) 454 | self.variables['files'] = [self.variables['prefix'] + 'RootListController.m'] 455 | with open(self.variables['prefix'] + 'RootListController.m', 'w') as out: 456 | out.write(Prefs_RootListController_m.format(self.variables['prefix'], self.variables['prefix'])) 457 | with open(self.variables['prefix'] + 'RootListController.h', 'w') as out: 458 | out.write(Prefs_RootListController_h.format(self.variables['prefix'])) 459 | elif type == 'app': 460 | self.variables['files'] = ['AppDelegate.m', 'RootViewController.m', 'main.m'] 461 | with open('AppDelegate.h', 'w') as out: 462 | out.write(AppDelegate_h) 463 | with open('AppDelegate.m', 'w') as out: 464 | out.write(AppDelegate_m) 465 | with open('RootViewController.h', 'w') as out: 466 | out.write(RootViewController_h) 467 | with open('RootViewController.m', 'w') as out: 468 | out.write(RootViewController_m) 469 | with open('main.m', 'w') as out: 470 | out.write(main_m) 471 | os.mkdir('Resources') 472 | _l = 'AppIcon29x29.png AppIcon29x29@2x.png AppIcon29x29@3x.png AppIcon40x40.png AppIcon40x40@2x.png ' \ 473 | 'AppIcon40x40@3x.png AppIcon50x50.png AppIcon50x50@2x.png AppIcon57x57.png AppIcon57x57@2x.png ' \ 474 | 'AppIcon57x57@3x.png AppIcon60x60.png AppIcon60x60@2x.png AppIcon60x60@3x.png AppIcon72x72.png ' \ 475 | 'AppIcon72x72@2x.png AppIcon76x76.png AppIcon76x76@2x.png'.split(' ') 476 | for file in _l: 477 | with open(f'Resources/{file}', 'wb') as out: 478 | out.write(b'') 479 | with open('Resources/Info.plist', 'w') as out: 480 | out.write(InfoPlist.format(self.variables['id'])) 481 | 482 | try: 483 | del self.variables['id'] 484 | except Exception: 485 | pass 486 | try: 487 | del self.variables['author'] 488 | except Exception: 489 | pass 490 | try: 491 | del self.variables['prefix'] 492 | except Exception: 493 | pass 494 | 495 | 496 | class ProjectEditor: 497 | def __init__(self): 498 | 499 | self.project_root_directory = os.getcwd() 500 | 501 | if os.path.exists('DragonMake'): 502 | with open('DragonMake') as f: 503 | self.config = yaml.safe_load(f) 504 | self.preexisting_config = True 505 | else: 506 | self.config = {} 507 | self.preexisting_config = False 508 | 509 | if self.config is None: # ?? 510 | self.config = {} 511 | self.preexisting_config = False 512 | 513 | def create_new_module(self): 514 | if not self.preexisting_config: 515 | project = Project(self.project_root_directory) 516 | project.create_new() 517 | self.config = project.variables 518 | 519 | mod = Module(self.config) 520 | mod.create_new(self.project_root_directory) 521 | self.config[mod.name] = mod.variables 522 | os.chdir(self.project_root_directory) 523 | 524 | 525 | def main(): 526 | editor = ProjectEditor() 527 | editor.create_new_module() 528 | with open('DragonMake', 'w') as f: 529 | f.write(yaml.dump(editor.config, Dumper=yaml.RoundTripDumper)) 530 | 531 | 532 | if __name__ == '__main__': 533 | main() 534 | -------------------------------------------------------------------------------- /src/dragon/lo.py: -------------------------------------------------------------------------------- 1 | from ruyaml import YAML 2 | import platform 3 | from urllib import request 4 | import json, os, ssl, sys, tarfile 5 | from tqdm import tqdm 6 | from dragon.util import dprintline, OutputColors, OutputWeight 7 | 8 | plat = platform.platform() 9 | host_os = plat.split('-')[0] 10 | host_arch = plat.split('-')[2] 11 | 12 | ssl._create_default_https_context = ssl._create_unverified_context 13 | 14 | def log(s: str, end: str = '\n') -> None: 15 | dprintline(OutputColors.Cyan, "llvm-ObjCS", OutputColors.White, OutputWeight.Normal, False, s) 16 | 17 | 18 | iurl = "https://api.github.com/repos/DragonBuild/llvm-objcs/releases/latest" 19 | 20 | 21 | class DownloadProgressBar(tqdm): 22 | def update_to(self, b=1, bsize=1, tsize=None): 23 | if tsize is not None: 24 | self.total = tsize 25 | self.update(b * bsize - self.n) 26 | 27 | 28 | def download_url(url, output_path): 29 | with DownloadProgressBar(unit='B', unit_scale=True, 30 | miniters=1, desc=url.split('/')[-1], ) as t: 31 | urllib.request.urlretrieve(url, filename=output_path, reporthook=t.update_to) 32 | 33 | 34 | def install_from_url(ctx, url: str): 35 | log(f'Downloading {url}, this may take a moment...') 36 | fname = os.environ["DRAGON_ROOT_DIR"] + '/tmp.tar.gz' 37 | download_url(url, fname) 38 | tar = tarfile.open(fname) 39 | 40 | log(f'Extracting into {os.environ["DRAGON_ROOT_DIR"]}' + '/llvm-objcs') 41 | try: 42 | os.makedirs(os.environ["DRAGON_ROOT_DIR"] + '/llvm-objcs') 43 | except OSError: 44 | pass 45 | tar.extractall(os.environ["DRAGON_ROOT_DIR"] + '/llvm-objcs') 46 | os.remove(fname) 47 | 48 | 49 | def fetch(): 50 | destination = os.environ['DRAGON_ROOT_DIR'] + '/llvm-objcs' 51 | ctx = ssl.create_default_context() 52 | ctx.check_hostname = False 53 | ctx.verify_mode = ssl.CERT_NONE 54 | response: dict = json.load(request.urlopen(iurl, context=ctx)) 55 | if os.path.exists(f'{destination}/metadata.yml'): 56 | with open(f'{destination}/metadata.yml', 'r') as fd: 57 | yaml = YAML(typ='safe') # default, if not specfied, is 'rt' (round-trip) 58 | metadata = yaml.load(fd) 59 | version = metadata['version'] 60 | if version == response['tag_name']: 61 | log('Latest LLVM-ObjCS build already installed') 62 | return 63 | for asset in response['assets']: 64 | n = asset['name'] 65 | n = n.replace('llvm-objcs-', '').replace('.tar.gz', '') 66 | op_sys = n.split('-')[0] 67 | arch = n.split('-')[1] 68 | if op_sys.lower() == host_os.lower() and arch.lower() == host_arch.lower(): 69 | log(f"Found build for {op_sys}-{arch}, installing") 70 | install_from_url(ctx, asset['browser_download_url']) 71 | return 72 | 73 | log(f"Couldn't find a build for {host_os}-{host_arch}") 74 | 75 | 76 | # tag format: llvm-objcs-0.0.1-llvm-17.0.0 77 | if __name__ == "__main__": 78 | if 'setup' in sys.argv[1] or 'update' in sys.argv[1]: 79 | fetch() 80 | -------------------------------------------------------------------------------- /src/dragon/prebuild.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/src/dragon/prebuild.py -------------------------------------------------------------------------------- /src/dragon/shscripts/building: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | clean_dir() 5 | { 6 | DNAME=$1 7 | PROJ=$2 8 | pushd $DNAME &> /dev/null 9 | if [[ -d $DRAGON_DIR && ! -f $DRAGON_DIR/.clean ]]; then 10 | prefix_print "Cleaning $PROJ" 11 | if [[ -z $DRAGON_DIR ]]; then 12 | drexit_reason "nil DRAGON_DIR" 13 | fi 14 | if [[ -f $DRAGON_DIR/DragonMake ]]; then 15 | cp $DRAGON_DIR/DragonMake .DragonMake 16 | fi 17 | rm -rf $DRAGON_DIR/* 18 | if [[ -f .DragonMake ]]; then 19 | mv .DragonMake $DRAGON_DIR/DragonMake 20 | fi 21 | touch $DRAGON_DIR/.clean 22 | fi 23 | popd &> /dev/null 24 | } 25 | 26 | validate_objcs() 27 | { 28 | stat "${DRAGON_ROOT_DIR}/llvm-objcs" || python3 -m dragon.lo setup 29 | } 30 | 31 | build() 32 | { 33 | # shellcheck disable=SC2046 34 | cd $(python3 -c "print('.' if '$1.ninja' in '$(ls | xargs)' else '$1')") || drexit 35 | python3 -c "exit(0 if ('${project_dirs}'.count('.')<2) else 1)" && mv "$(ls *.ninja | xargs | cut -d' ' -f1)" ./build.ninja 36 | if [[ -z $DRAGON_DIR ]]; then 37 | drexit_reason "nil DRAGON_DIR" 38 | fi 39 | if ! [[ -d $DRAGON_DIR/_/.dragonbuilding ]]; then 40 | rm -rf $DRAGON_DIR/_ 41 | fi 42 | 43 | python3 -m dragongen.cliutils needsobjcs && validate_objcs 44 | 45 | mkdir -p $DRAGON_DIR/_/.dragonbuilding $DRAGON_DIR/modules 46 | 47 | DNAME=$1 48 | PROJ=$2 49 | prefix_print "Building $PROJ" 50 | 51 | if ! [[ -e build.ninja ]]; then 52 | mv "${i}.ninja" build.ninja 53 | fi 54 | 55 | JOBS=1 56 | if [[ -x $(command -v nproc) ]]; then 57 | JOBS=$(nproc --all) 58 | elif [[ -x $(command -v sysctl) ]]; then 59 | JOBS=$(sysctl -n hw.ncpu) 60 | fi 61 | 62 | projroot="$(dirname "$DRAGON_DATA_DIR")" 63 | env $'NINJA_STATUS=\x1b[1;34m[Dragon] \x1b[35m[%f/%t] \x1b[34m(%e)\x1b[0m ' CLICOLOR_FORCE=1 ninja -j$JOBS $NINJA_ARGS || cleanbuildfail $projroot 64 | env $'NINJA_STATUS=\x1b[1;34m[Dragon] \x1b[34m>>>\x1b[0m ' CLICOLOR_FORCE=1 ninja -j$JOBS stage $NINJA_ARGS || cleanbuildfail $projroot 65 | 66 | if [[ $exportt -eq 1 ]]; then 67 | prefix_print "Generating compile_commands.json" 68 | ninja -t compdb > compile_commands.json 69 | fi 70 | 71 | cp -R $projroot/layout/* layout/* $DRAGON_DIR/_/ 2>/dev/null 72 | 73 | # mv build.ninja $DRAGON_DIR/ninja # Was this renamed "ninja" for use elsewhere? 74 | mv build.ninja $DRAGON_DIR/build.ninja 75 | if [[ $DNAME == "." ]]; then 76 | true 77 | else 78 | cp -R $DRAGON_DIR/_ "$DRAGON_DATA_DIR" 2>/dev/null 79 | 80 | if [[ $release -eq 1 ]]; then 81 | if command -v plutil &> /dev/null; then 82 | cmd=plutil 83 | elif command -v ply &> /dev/null; then 84 | cmd=ply 85 | elif command -v plistutill &> /dev/null; then 86 | cmd=plistutil 87 | fi 88 | 89 | if ! [[ -z $cmd ]]; then 90 | prefix_print "Converting plist/xml resources to binary" 91 | find "$DRAGON_DATA_DIR" \( -name \*.plist -o -name \*.strings \) | while read i; do 92 | head="$(od -c "$i" | head)" 93 | clean_head="${head//[^[:alpha:]]/}" 94 | magic_bytes="${clean_head:0:6}" 95 | 96 | if ! [[ $magic_bytes == bplist ]]; then 97 | if [[ $cmd == plutil ]]; then 98 | plutil -convert binary1 "$i" 99 | elif [[ $cmd == ply ]]; then 100 | ply -c binary "$i" 101 | else 102 | plistutil -i "$i" -f bin -o "$i" 103 | fi 104 | fi 105 | done 106 | fi 107 | fi 108 | fi 109 | 110 | cd "$projroot" || drexit 111 | } 112 | -------------------------------------------------------------------------------- /src/dragon/shscripts/dragoncolors: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export PrefixColor='\033[1;34m' 4 | export PackageColor='\033[1;32m' 5 | export GenColor='\033[1;33m' 6 | export BoldColor='\033[1;37m' 7 | export NC='\033[0m' 8 | 9 | prefix_print() 10 | { 11 | echo -e "${PrefixColor}[Dragon]${BoldColor} $1${NC}" 12 | } 13 | 14 | gen_print() 15 | { 16 | echo -e "${GenColor}[DragonGen]${BoldColor} $1${NC}" 17 | } 18 | 19 | pkg_print() 20 | { 21 | echo -e "${PackageColor}[Packager]${BoldColor} $1${NC}" 22 | } 23 | -------------------------------------------------------------------------------- /src/dragon/shscripts/generator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | generate() 4 | { 5 | echo $DGEN_DEBUG | grep "1" > /dev/null && gen_print "DragonGen Debug Enabled" 6 | 7 | echo $simtarg | grep "1" > /dev/null && gen_print "Targeting Simulator + simject" 8 | 9 | 10 | if [[ $release -eq 1 ]]; then 11 | gen_print "---" 12 | gen_print "Building For RELEASE" 13 | gen_print "(#ifdef DEBUG == false, #ifdef NDEBUG == true)" 14 | gen_print "---" 15 | else 16 | gen_print "---" 17 | gen_print "Building For DEBUG" 18 | gen_print "(#ifdef DEBUG == true, #ifdef NDEBUG = false)" 19 | gen_print "---" 20 | fi 21 | 22 | eval $(DGEN_DEBUG="${DGEN_DEBUG}" TARG_SIM="${simtarg}" RELEASE="${release}" ROOTLESS="${rootless}" python3 -m dragongen.generation) 23 | 24 | export TWEAK_NAME=$package_name 25 | export INSTALL_CMD=$install_command 26 | } 27 | -------------------------------------------------------------------------------- /src/dragon/shscripts/packaging: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | uninstall_package() 4 | { 5 | packid="" 6 | if [[ -z $1 ]]; then 7 | packid="$(python3 -m dragongen.cliutils packid)" 8 | else 9 | packid="$1" 10 | fi 11 | if [[ -z $packid ]]; then 12 | prefix_print "Couldn't autodetect package id. You can provide one manually via (e.g. dragon u com.mycompany.mytweak)" 13 | drexit 1 14 | fi 15 | prefix_print "Uninstalling \"${packid}\"" 16 | python3 -m dragon.device qr dpkg -r ${packid} 17 | } 18 | 19 | send_package() 20 | { 21 | if [[ -z $1 ]]; then 22 | prefix_print "No package provided. Exiting." 23 | drexit 1 24 | fi 25 | 26 | if [[ $simtarg -eq 1 ]]; then 27 | simstall 28 | drexit 0 29 | fi 30 | if [[ $USER == "mobile" ]]; then 31 | sudo dpkg -i ./packages/${GENOUTPUT} 32 | ${INSTALL_CMD} & 33 | drexit 0 34 | fi 35 | prefix_print "Installing..." 36 | OUTPUT=$1 37 | if [[ -z $OUTPUT ]]; then 38 | prefix_print "$1 not found" 39 | drexit 40 | fi 41 | 42 | connectionCheck="$(python3 -m dragon.device test)" 43 | if [[ $connectionCheck =~ "Failed" ]]; then 44 | echo "$connectionCheck" 45 | drexit 1 46 | fi 47 | 48 | eval $(python3 -m dragon.device get) 49 | 50 | if [[ -z $DRBIP ]]; then 51 | python3 -m dragon.device setup 52 | fi 53 | 54 | prefix_print "Copying package to device and running install commands" 55 | TMP_PATH="/tmp/dragon/packages" 56 | FILE_PATH="/tmp/dragon/$OUTPUT" 57 | python3 -m dragon.device qr mkdir -p $TMP_PATH 58 | (scp -P $DRBPORT ${OUTPUT} root@$DRBIP:$FILE_PATH && python3 -m dragon.device qr "dpkg -i $FILE_PATH; rm -r $(dirname $TMP_PATH)") || drexit 1 59 | } 60 | 61 | create_package() 62 | { 63 | pkg_print "Generating Package Structure" 64 | mv $DRAGON_DIR/_/.dragonbuilding $DRAGON_DIR/_/DEBIAN 65 | if [[ -f control ]]; then 66 | echo -e "$(cat control)" > $DRAGON_DIR/_/DEBIAN/control 2>/dev/null 67 | # TODO: remove this ASAP when DragonMake is generated by default for Theos projs 68 | # right now, there is no way to modify the control values w/o file modification 69 | if [[ $rootless == 1 ]]; then 70 | sed -i'' 's/Architecture: .*/Architecture: iphoneos-arm64/g' $DRAGON_DIR/_/DEBIAN/control 71 | fi 72 | else 73 | python3 -m dragongen.control DragonMake ./$DRAGON_DIR/_/DEBIAN/control 74 | fi 75 | if [[ $rootless == 1 ]]; then 76 | tmp=$(mktemp -d) 77 | find $DRAGON_DIR/_/ -mindepth 1 -maxdepth 1 ! -name 'DEBIAN' -exec mv {} $tmp \; 78 | mkdir -p $DRAGON_DIR/_/$PkgPrefix 79 | mv $tmp/* $DRAGON_DIR/_/$PkgPrefix 80 | rm -r $tmp 81 | fi 82 | du -d 0 $DRAGON_DIR/_ | xargs -I '{}' echo 'Installed-Size: {}' | cut -d ' ' -f 1,2 >> $DRAGON_DIR/_/DEBIAN/control 83 | chmod -R 0755 $DRAGON_DIR/_/DEBIAN/* 84 | mkdir -p $DRAGON_DIR/packages 85 | find . -type f -name '.DS_Store' -o -name '.dragonbuilding' -delete 86 | pkg_print "Building Package" 87 | perl $DRAGON_ROOT_DIR/src/dm.pl/dm.pl -Zgzip -z9 $DRAGON_DIR/_ $DRAGON_DIR/packages/ || return 88 | GENOUTPUT="$(ls $DRAGON_DIR/packages)" 89 | mkdir -p packages 90 | cp $DRAGON_DIR/packages/${GENOUTPUT} packages/${GENOUTPUT} 91 | echo "${GENOUTPUT}" > $DRAGON_DIR/last_package 92 | } 93 | -------------------------------------------------------------------------------- /src/dragon/shscripts/prerun_checks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PLATFORM=$(uname) 4 | 5 | if [[ $PLATFORM == Darwin ]]; then 6 | # MacOS 7 | if [[ -x $(command -v xcode-select) ]]; then 8 | if ! [[ -x $(command -v clang) ]]; then 9 | prefix_print "Please install Xcode before proceeding." 10 | drexit 1 11 | elif ! [[ -x $(command -v ldid) ]]; then 12 | prefix_print "ldid does not appear to be installed. Attempting to install now..." 13 | if [[ -x $(command -v apt) && -f /opt/procursus/.procursus_strapped ]]; then 14 | sudo apt update 15 | sudo apt install -y ldid 16 | elif [[ -x "$(command -v port)" ]]; then 17 | sudo port selfupdate 18 | sudo port install ldid 19 | elif [[ -x "$(command -v brew)" ]]; then 20 | brew update 21 | brew install ldid 22 | else 23 | read -p "Homebrew, which provides tools Dragon depends on, is not installed. Would you like to have it installed for you? [y/n]" hbrew 24 | if [[ $hbrew == "y" || $hbrew == "Y" || $hbrew == "yes" || $hbrew == "YES" || $hbrew == "Yes" ]] ; then 25 | bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" && brew install ldid 26 | else 27 | prefix_print "Homebrew provides tools Dragon depends on and thus is mandatory. Please install Homebrew before proceeding." 28 | drexit 1 29 | fi 30 | fi 31 | fi 32 | # iOS 33 | else 34 | if [[ -f /.procursus_strapped || -f /var/jb/.procursus_strapped ]]; then 35 | if ! [[ -x $(command -v sudo) ]]; then 36 | prefix_print "Please install sudo before proceeding." 37 | drexit 1 38 | fi 39 | 40 | # need ninja built specifically for iOS 41 | if python3 -c "import ninja" &> /dev/null; then 42 | python3 -m pip uninstall -y ninja 43 | if ! dpkg -l ninja &> /dev/null; then 44 | sudo apt update --allow-insecure-repositories 45 | sudo apt install -y ninja 46 | fi 47 | fi 48 | 49 | if ! [[ -x $(command -v clang) ]]; then 50 | sudo apt update --allow-insecure-repositories 51 | sudo apt install -y --allow-downgrades ca-certificates clang coreutils dpkg git grep gzip ldid odcctools perl plutil rsync 52 | elif ! [[ -x $(command -v ldid) ]]; then 53 | sudo apt update --allow-insecure-repositories 54 | sudo apt install -y --allow-downgrades ldid 55 | fi 56 | else 57 | prefix_print "Dragon only supports Procursus-based jailbreaks as Elucubratus doesn't have a 'ninja/ninja-build' package available." 58 | drexit 1 59 | fi 60 | fi 61 | # Linux 62 | elif [[ ${PLATFORM,,} == linux ]]; then 63 | DISTRO="unknown" 64 | if [[ -x "$(command -v apt)" ]]; then 65 | DISTRO="debian" 66 | elif [[ -x "$(command -v dnf)" ]]; then 67 | DISTRO="redhat" 68 | elif [[ -x "$(command -v pacman)" ]]; then 69 | DISTRO="arch" 70 | elif [[ -x "$(command -v zypper)" ]]; then 71 | DISTRO="suse" 72 | fi 73 | 74 | if ! [[ -x $(command -v sudo) ]]; then 75 | prefix_print "Please install sudo before proceeding." 76 | drexit 1 77 | elif ! [[ -d $DRAGON_ROOT_DIR/toolchain/linux/iphone/ && $(ls -A "$DRAGON_ROOT_DIR/toolchain/linux/iphone/") ]]; then 78 | case $DISTRO in 79 | debian) 80 | sudo apt update 81 | sudo apt install -y build-essential libtinfo5 ninja-build rsync curl perl git libxml2 82 | ;; 83 | arch) 84 | sudo pacman -Syy 85 | sudo pacman -S --needed --noconfirm base-devel ninja libbsd openssl rsync curl perl git libxml2 86 | # libtinfo5 equivalent (get from AUR) 87 | if ! pacman -Qs ncurses5-compat-libs > /dev/null; then 88 | git clone https://aur.archlinux.org/ncurses5-compat-libs.git 89 | cd ncurses5-compat-libs 90 | gpg --recv-keys "$(cat PKGBUILD | grep validpgp | grep -oP "(?<=').*(?=')")" 91 | MAKEFLAGS="-j$(nproc --all)" makepkg -sir --noconfirm && cd .. && rm -rf ncurses5-compat-libs 92 | fi 93 | ;; 94 | redhat) 95 | sudo dnf check-update 96 | sudo dnf group install -y "C Development Tools and Libraries" 97 | sudo dnf install -y ncurses-compat-libs lzma libbsd ninja-build rsync curl perl git libxml2 98 | ;; 99 | suse) 100 | sudo zypper refresh 101 | sudo zypper install -y -t pattern devel_basis 102 | sudo zypper install -y libbsd0 libncurses5 ninja rsync curl perl git libxml2 103 | ;; 104 | *) 105 | prefix_print "Your distro's current package manager is unknown and thus Dragon cannot install the dependencies you'll need." 106 | prefix_print "On Debian-based distros, the necessary dependencies are: build-essential libtinfo5 ninja-build rsync curl perl git libxml2." 107 | prefix_print "Additional dependencies may also be required depending on what your distro provides." 108 | ;; 109 | esac 110 | curl -sL https://github.com/L1ghtmann/llvm-project/releases/download/test-e99a150/iOSToolchain.tar.xz | tar -xJvf - -C $DRAGON_ROOT_DIR/toolchain/ 111 | elif ! [[ -x $DRAGON_ROOT_DIR/toolchain/linux/iphone/bin/ldid ]]; then 112 | prefix_print "You appear to be missing ldid, but have the toolchain. Not sure how we got here honestly ..." 113 | prefix_print "Please build or download ldid from https://github.com/ProcursusTeam/ldid and place it in $DRAGON_ROOT_DIR/toolchain/linux/iphone/bin/." 114 | drexit 1 115 | fi 116 | else 117 | prefix_print "Note: '$PLATFORM' is currently unsupported by Dragon." 118 | prefix_print "If you want to try running dragon, you will need, at a minimum, Apple's clang and ldid." 119 | prefix_print "Additional dependencies may also be required depending on what your platform provides." 120 | fi 121 | -------------------------------------------------------------------------------- /src/dragon/shscripts/remote: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | remote() 4 | { 5 | set -e 6 | source $DRAGON_ROOT_DIR/state/remoteip 7 | prefix_print "Checking for Dragon on remote server..." 8 | DRG='$(python3 -c "import site; print(site.USER_BASE)")/bin/dragon' 9 | DRG_CHECK=$(ssh ${USERN}@${IPA} "[[ -x $DRG ]] && echo $DRG") 10 | if [[ -z $DRG_CHECK ]]; then 11 | prefix_print "Dragon is not installed on the remote server. Please install it with 'pip3 install dragon' before proceeding." 12 | drexit 13 | else 14 | prefix_print "Remote dragon is $DRG_CHECK" 15 | fi 16 | prefix_print "Determining remote directory path..." 17 | RDIR=$(ssh ${USERN}@${IPA} 'echo $HOME/.dragon/server/ && mkdir -p $HOME/.dragon/server/') 18 | prefix_print "Remote directory path is $RDIR" 19 | prefix_print "Syncing project directory..." 20 | rsync -aL --exclude '.dragon' --exclude 'packages' --exclude '.idea' $PWD ${USERN}@${IPA}:$RDIR 21 | RPROJ_DIR=${RDIR}$(basename $PWD) 22 | prefix_print "Remote project directory is $RPROJ_DIR" 23 | prefix_print "Starting Build..." 24 | ssh ${USERN}@${IPA} -t "cd $RPROJ_DIR && $DRG $*" 25 | set +e 26 | } 27 | -------------------------------------------------------------------------------- /src/dragon/shscripts/simulator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | simfail() 4 | { 5 | prefix_print "Simulator Injection Failed." 6 | prefix_print "Please file an issue with project details." 7 | prefix_print "https://github.com/DragonBuild/dragon" 8 | drexit 1 9 | } 10 | 11 | simsetup() 12 | { 13 | mkdir -p $DRAGON_ROOT_DIR/simulator/load 14 | hold=$PWD 15 | cd $DRAGON_ROOT_DIR/src/simject || simfail 16 | dragon c b 17 | cd $hold || simfail 18 | } 19 | 20 | simstall() 21 | { 22 | test -f $DRAGON_ROOT_DIR/simulator/resim || simsetup 23 | prefix_print "Installing files to the simulator..." 24 | cp $DRAGON_DIR/_/Library/MobileSubstrate/DynamicLibraries/* $DRAGON_ROOT_DIR/simulator/load || cleanbuildfail 25 | prefix_print "Respringing..." 26 | resim 27 | } 28 | -------------------------------------------------------------------------------- /src/dragon/shscripts/upgrader: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DPREVERS=$(python3 -c 'from dragon.util import version; print(version())') 4 | 5 | python3 -m pip install --upgrade dragon 6 | 7 | DPOSTVERS=$(python3 -c 'from dragon.util import version; print(version())') 8 | 9 | if [[ $DPOSTVERS == $DPREVERS ]]; then 10 | prefix_print "Latest version of dragon already installed" 11 | else 12 | prefix_print "Updated dragon. Updating resources..." 13 | python3 -m dragon.wizard 14 | fi 15 | -------------------------------------------------------------------------------- /src/dragon/shscripts/util: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Colors we load in 4 | TOOLPATH=$(python3 -c 'from dragon.util import tool_path; print(tool_path())') 5 | source $TOOLPATH/dragoncolors 6 | 7 | drexit_reason() 8 | { 9 | prefix_print $1 10 | drexit 11 | } 12 | 13 | drexit() 14 | { 15 | # This function resets terminal colors, fixes stty (just in case), and then exits with arg0's value 16 | # It should be called on *EVERY* exit, no matter what. 17 | echo -e "${NC}" 18 | stty sane 19 | python3 -m dragon.update_check 20 | exit $1 21 | } 22 | 23 | cleanbuildfail() 24 | { 25 | # Run this when we hit an error building, so we can clean things up. 26 | # This needs to eventually be updated for the new {name}.ninja format 27 | # Currently it'll just leave everything sitting out 28 | 29 | # shellcheck disable=SC2154 30 | prefix_print "Build failed" 31 | prefix_print "Cleaning Up" 32 | find . -name '.clean' -type f -delete 33 | if [[ $norm -eq 1 ]]; then 34 | mkdir -p $DRAGON_DIR/ninja && mv build.ninja $DRAGON_DIR/ninja/build.ninja 35 | if ! [[ -z $1 ]]; then 36 | cd $1 || drexit 37 | mkdir -p $DRAGON_DIR/ninja && mv build.ninja $DRAGON_DIR/ninja/build.ninja 38 | fi 39 | else 40 | find . -type f -name "*.ninja" -delete 41 | fi 42 | drexit 1 43 | } 44 | 45 | setupRemote() 46 | { 47 | # remote.py | remote.sh 48 | prefix_print "Enter Server IP:" 49 | read IPA 50 | mkdir -p $DRAGON_ROOT_DIR/state 51 | echo "export IPA=\"$IPA\"" > $DRAGON_ROOT_DIR/state/remoteip 52 | prefix_print "Enter Username:" 53 | read USERN 54 | if [[ -z $USERN ]]; then 55 | USERN="root" 56 | fi 57 | echo "export USERN=\"$USERN\"" >> $DRAGON_ROOT_DIR/state/remoteip 58 | } 59 | 60 | update() 61 | { 62 | # update.sh 63 | hold="$PWD" 64 | cd $DRAGON_ROOT_DIR || drexit 65 | git pull 66 | git submodule update --init --recursive 67 | cd "$hold" || drexit 68 | } 69 | 70 | usage() 71 | { 72 | echo -e "" 73 | echo -e "${PrefixColor}dragon v${DRAGON_VERS} ${BoldColor}-=-=-${NC}" 74 | echo -e " usage: dragon [commands]" 75 | echo "" 76 | echo -e "${PrefixColor}Building ${BoldColor}-=-=-${NC}" 77 | echo -e " ${PackageColor}n${NC} | new|nic|edit - ${BoldColor}Open the project editor (For creating new projects/adding to existing ones)${NC}" 78 | echo -e " ${PackageColor}do${NC} - ${BoldColor}Build and Install${NC}" 79 | echo -e " ${PackageColor}c${NC} | clean - ${BoldColor}Clear build cache${NC}" 80 | echo -e " ${PackageColor}b${NC} | build|make - ${BoldColor}Compile, link, and package your project${NC}" 81 | echo -e " ${PackageColor}g${NC} | gen|generate - ${BoldColor}Create build files without actually building${NC}" 82 | echo "" 83 | echo -e "${PrefixColor}Configuration ${BoldColor}-=-=-${NC}" 84 | echo -e " ${PackageColor}r${NC} | release - ${BoldColor}Create a release build (defines NDEBUG, enables 'releaseflags' value)${NC}" 85 | echo -e " ${PackageColor}ro${NC} | rootless - ${BoldColor}Build for rootless package scheme${NC}" 86 | # Debug commands. We should print them _somewhere_ but not here :thonk: 87 | # echo -e " ${PackageColor}ddebug${NC} - ${BoldColor}Enable verbose debug logs${NC}" 88 | # echo -e " ${PackageColor}vn${NC} - ${BoldColor}Enable verbose ninja(-build)${NC}" 89 | # echo -e " ${PackageColor}vd${NC} - ${BoldColor}Enable verbose dragon${NC}" 90 | # echo -e " ${PackageColor}vg${NC} - ${BoldColor}Enable verbose dragongen${NC}" 91 | # echo -e " ${PackageColor}norm${NC} - ${BoldColor}Retain build.ninja after building${NC}" 92 | echo "" 93 | echo "Common invocations are 'dragon c b i' (clean all, build, install) or 'dragon do' (build and install)" 94 | echo "" 95 | echo -e "${PrefixColor}Distribution ${BoldColor}-=-=-${NC}" 96 | echo -e " ${PackageColor}i${NC} | install - ${BoldColor}Install latest build to build device${NC}" 97 | echo -e " ${PackageColor}u${NC} | uninstall - ${BoldColor}Uninstall latest build from build device${NC}" 98 | echo -e " ${PackageColor}sn${NC} | send - ${BoldColor}Install an arbitrary .deb to the device${NC}" 99 | echo -e " ${PackageColor}sim${NC} | simulator - ${BoldColor}Install to the simulator instead of a device (e.g. dragon c b i sim)${NC}" 100 | echo "" 101 | echo -e "${PrefixColor}Device Management ${BoldColor}-=-=-${NC}" 102 | echo -e " ${PackageColor}s${NC} | device - ${BoldColor}Set build device IP/Port${NC}" 103 | echo -e " ${PackageColor}rs${NC} | respring - ${BoldColor}Respring the current build device${NC}" 104 | echo -e " ${PackageColor}dr${NC} | devicerun - ${BoldColor}Run anything after this flag on device${NC}" 105 | echo "" 106 | echo -e "${PrefixColor}Tools ${BoldColor}-=-=-${NC}" 107 | echo -e " ${PackageColor}lo${NC} | objcs - ${BoldColor}Configure llvm-objcs for use with dragon${NC}" 108 | echo -e " ${PackageColor}debug [Process]${NC} - ${BoldColor}Start a debugging server on device and connect to it (Can be used with the install flag as well)${NC}" 109 | echo -e " ${PackageColor}exp${NC} | export - ${BoldColor}Tell ninja to create a compile_commands.json${NC}" 110 | echo -e " ${PackageColor}sr${NC} | rconf - ${BoldColor}Setup remote project build${NC}" 111 | echo -e " ${PackageColor}up${NC} | update|upgrade - ${BoldColor}Update dragon${NC}" 112 | # echo -e " ${PackageColor}test${NC} - ${BoldColor}Run the test suite${NC}" 113 | # echo -e " ${PackageColor}time${NC} - ${BoldColor}Print time for each bash subcommand to run${NC}" 114 | echo "" 115 | echo -e "${PrefixColor}Misc ${BoldColor}-=-=-${NC}" 116 | echo -e " ${PackageColor}v${NC} | -v - ${BoldColor}Prints version information${NC}" 117 | echo -e " ${PackageColor}h${NC} | -h|help - ${BoldColor}Prints this help page${NC}" 118 | echo "" 119 | echo -e "${PrefixColor}-=-=-${NC}" 120 | echo "" 121 | echo "dragon v${DRAGON_VERS}" 122 | echo "created by cynder, made possible through crucial contributions from Lorenzo Pane, Lightmann" 123 | echo "with contributions from: iCrazeiOS, Squidkingdom, Amy While, Conor, MrGcGamer, jaidan, Diego Magdaleno" 124 | # <3 125 | } 126 | -------------------------------------------------------------------------------- /src/dragon/shscripts/variables: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # These are the flags for the "state" of the program 4 | # Consider it a "checklist." Anything that gets set to 1 is something we need to get done and in the proper order 5 | export install=0 6 | export build=0 7 | export gen=0 8 | export clean=0 9 | export exportt=0 10 | export norm=0 11 | export debug=0 12 | export debugproc="" 13 | export ddebug=0 14 | export DRAGON_DPKG=1 15 | export DRAGON_POSTINST=0 16 | 17 | export simtarg=0 18 | 19 | export release=0 20 | export rootless=0 21 | export PkgPrefix="" 22 | 23 | export ToolchainPath="" 24 | export ToolchainPrefix="" 25 | 26 | # This controls ninja colors. 27 | export NINJA_STATUS="\x1b[34m[Dragon] [%f/%t] (%e)" 28 | -------------------------------------------------------------------------------- /src/dragon/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import shutil, sys, os, timeit, yaml 4 | from math import sin, cos, radians 5 | from shared.util import system 6 | 7 | TestDict = yaml.safe_load(open(os.environ['DRAGON_ROOT_DIR'] + '/internal/tests.yml')) 8 | projects = TestDict['ProjectTests'] 9 | 10 | 11 | def bench(): 12 | product = 1.0 13 | for counter in range(1, 1000, 1): 14 | for dex in list(range(1, 360, 1)): 15 | angle = radians(dex) 16 | product *= sin(angle) ** 2 + cos(angle) ** 2 17 | return product 18 | 19 | 20 | def main(): 21 | tests = { 22 | 23 | } 24 | times = { 25 | 26 | } 27 | testing_dir = os.environ['DRAGON_ROOT_DIR'] + '/testing/' 28 | if os.path.isdir(testing_dir): 29 | shutil.rmtree(testing_dir) 30 | os.makedirs(testing_dir, exist_ok=True) 31 | os.chdir(testing_dir) 32 | 33 | # print('Doing Benchmarks') 34 | # perform_benchmark() 35 | 36 | for category in projects: 37 | cattests = {} 38 | cattimes = {} 39 | for i in projects[category]: 40 | os.chdir(testing_dir) 41 | print(os.getcwd()) 42 | system(''' 43 | echo $PWD 44 | git clone %s 45 | ''' % i, sys.stdout, sys.stderr) 46 | dirName = i.split('/')[-1] 47 | os.chdir(dirName) 48 | print(os.getcwd()) 49 | print(f"---\n Testing {dirName} \n---") 50 | # time.sleep(5) 51 | s = timeit.default_timer() 52 | passed = system('dragon c b', sys.stdout, sys.stderr) == 0 53 | s = timeit.default_timer() - s 54 | print(s) 55 | print('-+- Passed -+-' if passed else '-!- Failed -!-') 56 | cattests[dirName] = passed 57 | cattimes[dirName] = s 58 | os.chdir(testing_dir) 59 | if passed: 60 | shutil.rmtree(dirName) 61 | tests[category] = cattests 62 | times[category] = cattimes 63 | 64 | print('\n\n---\n') 65 | 66 | os.system('uname -a') 67 | 68 | total = 0 69 | passed = 0 70 | 71 | for category in tests: 72 | print(f'\n{category} Project Build Tests') 73 | for i in tests[category]: 74 | print( 75 | f'{i} : {"Passed" if tests[category][i] else "Failed"} in {times[category][i]} ') 76 | passed += sum(tests[category].values()) 77 | total += len(tests[category]) 78 | 79 | print(f'\n{passed}/{total} passed\n---\n\n') 80 | 81 | 82 | if __name__ == '__main__': 83 | main() 84 | -------------------------------------------------------------------------------- /src/dragon/update_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json, os, urllib.request 4 | from packaging.version import Version 5 | 6 | if __name__ == '__main__': 7 | endpoint = "https://pypi.org/pypi/dragon/json" 8 | # noinspection PyBroadException 9 | try: 10 | with urllib.request.urlopen(endpoint, timeout=1) as url: 11 | data = json.loads(url.read().decode(), strict=False) 12 | if Version(os.environ['DRAGON_VERS']) < Version(data['info']['version']): 13 | print('\nAn update is available!\nGrab it with `dragon update`') 14 | else: 15 | if 'UCDBG' in os.environ.keys(): 16 | print(f'Remote Version: {data["info"]["version"]} | Local Version: {os.environ["DRAGON_VERS"]}') 17 | except Exception: 18 | pass 19 | -------------------------------------------------------------------------------- /src/dragon/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os.path as path 4 | import pkg_resources 5 | 6 | 7 | def version() -> str: 8 | return pkg_resources.get_distribution('dragon').version 9 | 10 | 11 | def tool_path() -> str: 12 | return path.dirname(__file__) + '/shscripts/' 13 | 14 | 15 | def deployable_path() -> str: 16 | return path.dirname(__file__) + '/config/' 17 | -------------------------------------------------------------------------------- /src/dragon/wizard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os, shutil, sys 4 | from .util import deployable_path 5 | 6 | 7 | def log(s: str, end: str = '\n') -> None: 8 | print(s, file=sys.stderr, end=end) 9 | sys.stderr.flush() 10 | 11 | 12 | def setup_wizard(): 13 | log(f'installing dragon v{os.environ["DRAGON_VERS"]}') 14 | log('=========================', end='\n\n') 15 | dragon_root_dir = os.environ['DRAGON_ROOT_DIR'] 16 | try: 17 | os.mkdir(dragon_root_dir) 18 | except FileExistsError: 19 | pass 20 | 21 | os.chdir(dragon_root_dir) 22 | 23 | for repo in ('lib', 'include', 'frameworks', 'sdks', 'src'): 24 | if os.path.isdir(repo) and not os.path.isdir(f'{repo}/.git'): 25 | shutil.rmtree(repo) 26 | os.system(f'git clone --recursive https://github.com/DragonBuild/{repo}') 27 | elif os.path.isdir(repo) and os.path.isdir(f'{repo}/.git'): 28 | os.chdir(repo) 29 | os.system('git pull origin $(git rev-parse --abbrev-ref HEAD)') 30 | os.chdir(dragon_root_dir) 31 | else: 32 | os.system(f'git clone --recursive https://github.com/DragonBuild/{repo}') 33 | 34 | log('Deploying internal configuration') 35 | try: 36 | shutil.rmtree('./internal') 37 | except FileNotFoundError: 38 | pass 39 | shutil.copytree(deployable_path(), dragon_root_dir + '/internal') 40 | 41 | try: 42 | os.mkdir(dragon_root_dir + '/toolchain') 43 | except FileExistsError: 44 | pass 45 | log('Done!') 46 | 47 | 48 | if __name__ == '__main__': 49 | setup_wizard() 50 | os.system('dragon v') 51 | -------------------------------------------------------------------------------- /src/dragongen/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBuild/dragon/b89e84cd2538c47b137caf01c93bd46a3efb836f/src/dragongen/__init__.py -------------------------------------------------------------------------------- /src/dragongen/bfilter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import yaml, os, sys 4 | # from shared.util import dbstate 5 | 6 | # This script will get called w/ 7 | # argc=3 argv[0] argv[1] argv[2] 8 | # python3 $DRAGON_ROOT_DIR/internal/bfilter.py DragonMake projectName 9 | 10 | def main(): 11 | if os.path.exists(sys.argv[1]): 12 | with open(sys.argv[1]) as f: 13 | config = yaml.safe_load(f) 14 | 15 | # dbstate("Packager", "Creating Filter for " + sys.argv[2]) 16 | 17 | filter_dict = config[sys.argv[2]]['filter'] 18 | print(filter_serialize(filter_dict)) 19 | 20 | 21 | def filter_serialize(filter_dict): 22 | # {'executables':['SpringBoard']} 23 | # out: { Filter = { Executables = ( "SpringBoard" ); }; } 24 | ind = "{ Filter = { " 25 | for i in filter_dict: 26 | ind += i.capitalize() + ' = ( ' + ", ".join(["\"" + j + "\"" for j in filter_dict[i]]) + ' );' 27 | ind += ' }; }' 28 | return ind 29 | 30 | 31 | main() 32 | -------------------------------------------------------------------------------- /src/dragongen/cliutils.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | import ruyaml 4 | import os 5 | 6 | 7 | if __name__ == "__main__": 8 | if 'packid' in sys.argv[1]: 9 | if os.path.exists('DragonMake'): 10 | with open('DragonMake') as fp: 11 | data = ruyaml.safe_load(fp) 12 | if 'package' in data: 13 | print(data['package']) 14 | exit(0) 15 | if 'id' in data: 16 | print(data['id']) 17 | exit(0) 18 | if 'Package' in data: 19 | print(data['Package']) 20 | exit(0) 21 | if os.path.exists('control'): 22 | with open('control') as fp: 23 | data = ruyaml.safe_load(fp) 24 | if 'Package' in data: 25 | print(data['Package']) 26 | exit(0) 27 | elif os.path.exists('layout/DEBIAN/control'): 28 | with open('layout/DEBIAN/control') as fp: 29 | data = ruyaml.safe_load(fp) 30 | if 'Package' in data: 31 | print(data['Package']) 32 | exit(0) 33 | exit(1) 34 | 35 | if 'needsobjcs' in sys.argv[1]: 36 | if os.path.exists('DragonMake'): 37 | with open('DragonMake') as fp: 38 | data = ruyaml.safe_load(fp) 39 | if 'objcs' in data: 40 | exit(0) 41 | exit(1) 42 | -------------------------------------------------------------------------------- /src/dragongen/control.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import yaml, os, sys 4 | from shared.util import dbstate, dbwarn, dberror 5 | 6 | # This script will get called w/ 7 | # argc=3 argv[0] argv[1] argv[2] 8 | # python3 $DRAGON_ROOT_DIR/internal/control.py DragonMake ./$DRAGON_DIR/_/DEBIAN/control 9 | def main(): 10 | dbstate("Packager", "Pulling 'control' values from DragonMake") 11 | 12 | keys = { 13 | 'name': 'Name', 14 | 'id': 'Package', 15 | 'author': 'Author', 16 | 'auth': 'Author', 17 | 'maintainer': 'Maintainer', 18 | 'mtn': 'Maintainer', 19 | 'version': 'Version', 20 | 'vers': 'Version', 21 | 'depends': 'Depends', 22 | 'deps': 'Depends', 23 | 'provides': 'Provides', 24 | 'conflicts': 'Conflicts', 25 | 'architecture': 'Architecture', 26 | 'section': 'Section', 27 | 'package': 'Package', 28 | 'description': 'Description', 29 | 'desc': 'Description', 30 | 'icon': 'Icon', 31 | 'depiction': 'Depiction', 32 | 'sldepiction': 'Sileodepiction', 33 | 'sileodepiction': 'Sileodepiction' 34 | } 35 | 36 | defs = { 37 | 'Section': 'Tweaks', 38 | 'Description': 'A cool MobileSubstrate Tweak', 39 | 'Version': '0.0.1', 40 | 'Architecture': 'iphoneos-arm', 41 | 'Depends': 'mobilesubstrate' # This is a blind guess, maybe we can improve this logic? 42 | } 43 | 44 | filenames = [ # extrainst_? 45 | 'preinst', 46 | 'postinst', 47 | 'prerm', 48 | 'postrm' 49 | ] 50 | # load in the DragonMake file 51 | if os.path.exists(sys.argv[1]): 52 | with open(sys.argv[1]) as f: 53 | config = yaml.safe_load(f) 54 | 55 | else: 56 | dberror("Packager", 'DragonMake not found, not sure how we got here honestly.') 57 | dberror("Packager", 'If you believe this message is in error, file an issue.') 58 | dberror("Packager", 'https://github.com/DragonBuild/dragon') 59 | 60 | control = {keys[key]: value for (key, value) in config.items() if key in keys} 61 | 62 | # Fallbacks section 63 | if 'Name' not in control: 64 | control['Name'] = os.path.basename(os.getcwd()) 65 | # Warn for this bc it's kinda important 66 | dbwarn("Packager", f'No "name:" key in DragonMake, guessing based on directory name ({control["Name"]})') 67 | 68 | if 'Package' not in control: 69 | control['Package'] = f'com.yourcompany.{control["Name"].lower()}' 70 | # Warn for this too, it's fairly important 71 | dbwarn("Packager", 'No "id:" key in DragonMake, creating default based on `Name:` key') 72 | 73 | if 'Author' not in control: 74 | control['Author'] = os.getlogin() 75 | dbwarn("Packager", f'No "Author:" key in DragonMake, guessing based on current user ({control["Author"]})') 76 | 77 | if 'Maintainer' not in control: 78 | control['Maintainer'] = control['Author'] 79 | dbwarn("Packager", 'No "Maintainer:" key in DragonMake, creating default based on `Author:` key') 80 | 81 | if int(os.environ["rootless"]) == 1: 82 | control['Architecture'] = 'iphoneos-arm64' 83 | 84 | # print(defs.update(control)) 85 | # print(control) 86 | # print(dict(defs, **control)) 87 | 88 | with open(sys.argv[2], 'w') as out: 89 | out.truncate() 90 | out.seek(0) 91 | yaml.dump(dict(defs, **control), out, default_flow_style=False) 92 | 93 | for name in filenames: 94 | if name in config: 95 | dbstate("Packager", f'Creating {name}') 96 | with open(os.path.dirname(os.sys.argv[2]) + f'/{name}', 'w') as out: 97 | out.truncate() 98 | out.seek(0) 99 | out.writelines(config[name]) 100 | 101 | 102 | if __name__ == "__main__": 103 | main() 104 | -------------------------------------------------------------------------------- /src/dragongen/generation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | 5 | DragonGen.py 6 | 7 | (c) 2020 cynder 8 | Please refer to the LICENSE file included with this project regarding the usage of code herein. 9 | 10 | this file looks moderately sane thanks to wonderful work by @l0renzo 11 | 12 | any remaining or new travesties are my own fabrications. 13 | 14 | """ 15 | 16 | import traceback, platform, yaml, termios, tty 17 | from collections import namedtuple 18 | from datetime import datetime 19 | from typing import TextIO 20 | from .variable_types import ProjectVars 21 | from .util import * 22 | from .toolchain import Toolchain 23 | from buildgen.generator import BuildFileGenerator 24 | from shared.util import dbstate, dbwarn, dberror 25 | 26 | # Rules and defaults 27 | _LAZY_RULES_DOT_YML: dict = None 28 | _LAZY_DEFAULTS_DOT_YML: dict = None 29 | 30 | _IS_THEOS_MAKEFILE_ = False 31 | 32 | _RELEASE_BUILD = False 33 | 34 | # These are used like so: 35 | # a_build_object = Build("output files here", "rule name here", "input files here") 36 | # outputs = a_build_object.outputs 37 | 38 | Build = namedtuple('Build', ['outputs', 'rule', 'inputs']) 39 | Comment = namedtuple('Comment', ['fstring']) 40 | Rule = namedtuple('Rule', ['name', 'description', 'command']) 41 | Var = namedtuple('Var', ['key']) 42 | Default = namedtuple('Default', ['targets']) 43 | ___ = object() # Newline 44 | 45 | 46 | # Get rule by name from rules.yml 47 | def get_generic_rule(name: str) -> Rule: 48 | return Rule(name, rules(name, 'desc'), rules(name, 'cmd')) 49 | 50 | 51 | class Generator(object): 52 | 53 | def __init__(self, config: dict, module_name: str, target_platform: str): 54 | self.config: dict = config 55 | self.module_name: str = module_name 56 | self.target_platform: str = target_platform 57 | self.project_variables: ProjectVars = None 58 | 59 | def write_output_file(self, stream: TextIO): 60 | """ 61 | Evaluate outline with variables and write ninja file to given IO stream 62 | 63 | Keyword arguments: 64 | stream -- IO stream to which the ninja data should be writen 65 | """ 66 | 67 | # Compute project variables 68 | self.project_variables: ProjectVars = ProjectVars( 69 | self.generate_vars(self.config[self.module_name], self.target_platform)) 70 | 71 | # Generate the outline 72 | outline = self.generate_ninja_outline() 73 | 74 | # Set up the output file generator (buildgen) 75 | gen = BuildFileGenerator(stream) 76 | 77 | # Iterate through the outline and write it with buildgen to the ninja/makefile 78 | for item in outline: 79 | if item == ___: 80 | gen.newline() 81 | continue 82 | if isinstance(item, Comment): 83 | gen.comment(item.fstring) 84 | continue 85 | if isinstance(item, Var): 86 | gen.variable(item.key, str(self.project_variables[item.key])) 87 | continue 88 | if isinstance(item, Rule): 89 | gen.rule(item.name, 90 | description=item.description, 91 | command=item.command) 92 | continue 93 | if isinstance(item, Build): 94 | gen.build(item.outputs, item.rule, item.inputs) 95 | continue 96 | if isinstance(item, Default): 97 | gen.default(['$build_target_file']) 98 | 99 | def generate_vars(self, module_variables: dict, target: str) -> dict: 100 | """ 101 | Generate ProjectVars object for a project 102 | 103 | Keyword arguments: 104 | module_variables -- dict of explicitly set variables for this project 105 | target -- target platform 106 | 107 | Raises: KeyError 108 | """ 109 | 110 | if 'for' in module_variables: 111 | target = module_variables['for'] 112 | 113 | # Load in internal defaults 114 | # These are ones that really probably shouldn't be touched often as they 115 | # serve to slap together all the variables we *do* touch 116 | project_dict: dict = get_default_section_dict('InternalDefaults') 117 | 118 | if _IS_THEOS_MAKEFILE_: 119 | project_dict.update({ 120 | 'theosshim': '-include$$DRAGON_ROOT_DIR/include/PrefixShim.h -w' 121 | }) 122 | 123 | # Setup with default vars 124 | project_dict.update(get_default_section_dict('Defaults')) # Universal 125 | try: 126 | # Apply Type Variables 127 | project_dict.update(get_default_section_dict('Types', 128 | module_variables['type'], 'variables')) # Type-based 129 | except KeyError as ex: 130 | try: 131 | # Cast type to lowercase and try again 132 | project_dict.update(get_default_section_dict('Types', 133 | module_variables['type'].lower(), 'variables')) 134 | except KeyError: 135 | # They either didn't include a type variable, or they misspelled the 136 | # type they used. 137 | raise ex 138 | 139 | # Load rootless config 140 | if int(os.environ['rootless']) == 1: 141 | project_dict.update(get_default_section_dict('Rootless')) 142 | 143 | # Apply the set of variables the user included on this module 144 | project_dict.update(module_variables) 145 | # Apply the module name 146 | project_dict['name'] = self.module_name 147 | 148 | # We allow the user to create their own Target and all sections 149 | # Iterate through defaults.yml, the module's specific variables, and the root of 150 | # the DragonMake 151 | for source in get_default_section_dict(), module_variables, self.config: 152 | if 'all' in source: 153 | project_dict.update(source['all']) 154 | if 'Targets' in source and target in source['Targets'] and 'all' in source['Targets'][target]: 155 | project_dict.update(source['Targets'][target]['all']) 156 | 157 | project_dict.update(module_variables) 158 | 159 | # MACHINE checks 160 | for d, i in enumerate(project_dict['archs']): 161 | if 'MACHINE' in i: 162 | project_dict['archs'][d] = platform.machine() 163 | 164 | if 'triple' in project_dict and project_dict['triple'] != '': 165 | project_dict['triple'] = '-target ' + os.popen('clang -print-target-triple').read().strip() \ 166 | if 'MACHINE' in project_dict['triple'] else '-target ' + project_dict['triple'] 167 | 168 | # A few variables that need to be renamed 169 | NINJA_KEYS = { 170 | 'location': 'install_location', 171 | 'btarg': 'targ', 172 | 'header_includes': 'include', 173 | 'typeldflags': 'ldflags', 174 | 'lopt': 'lopts' 175 | } 176 | # Rename them 177 | project_dict.update({key: project_dict[NINJA_KEYS[key]] 178 | for key in NINJA_KEYS 179 | if NINJA_KEYS[key] in project_dict}) 180 | 181 | project_dict['lowername'] = str(project_dict['name']).lower() 182 | 183 | # Apply framework/lib search and additional search dirs 184 | project_dict['fwSearch'] = project_dict['fw_dirs'] \ 185 | + (project_dict['additional_fw_dirs'] 186 | if project_dict['additional_fw_dirs'] 187 | else []) 188 | project_dict['libSearch'] = project_dict['lib_dirs'] \ 189 | + (project_dict['additional_lib_dirs'] 190 | if project_dict['additional_lib_dirs'] 191 | else []) 192 | 193 | # Specify toolchain paths 194 | use_objcs = 'objcs' in project_dict 195 | 196 | sys = platform.system() 197 | if sys == 'Darwin': 198 | plat = platform.platform() 199 | if 'iP' in plat: 200 | toolchain = Toolchain() 201 | else: 202 | toolchain = Toolchain.locate_macos_toolchain(use_objcs) 203 | # elif WINDOWS IS NOT A REAL OPERATING SYSTEM ::::) 204 | elif sys == 'Linux': 205 | toolchain = Toolchain.locate_linux_toolchain(use_objcs) 206 | else: 207 | toolchain = Toolchain() 208 | 209 | if toolchain is None: 210 | dberror("Dragon Gen", "Could not locate any usable toolchain or even determine the existence of clang.") 211 | dberror("Dragon Gen", "If you're on macOS, install XCode and the Command Line Tools package") 212 | dberror("Dragon Gen", "If you're on linux, install L1ghtmann's iOS toolchain to ~/.dragon/toolchain") 213 | dberror("Dragon Gen", "You can also add the key 'objcs': True to your DragonMake module to have dragon automatically" 214 | "install its internal toolchain, however note this isn't really reccomended for non-objcs " 215 | "projects") 216 | exit(64) 217 | 218 | if 'cc' not in project_dict: 219 | project_dict['cc'] = toolchain.clang 220 | if 'cxx' not in project_dict: 221 | project_dict['cxx'] = toolchain.clangpp 222 | if 'lipo' not in project_dict: 223 | project_dict['lipo'] = toolchain.lipo 224 | if 'dsym' not in project_dict: 225 | project_dict['dsym'] = toolchain.dsym 226 | if 'ld' not in project_dict: 227 | project_dict['ld'] = toolchain.ld 228 | if 'codesign' not in project_dict: 229 | project_dict['codesign'] = toolchain.codesign 230 | 231 | # TODO: lazy hack 232 | if 'cxxflags' in project_dict: 233 | project_dict['cxx'] = project_dict['cxx'] + ' ' + project_dict['cxxflags'] 234 | 235 | # TODO: move this to arglist maybe 236 | if project_dict['sysroot']: 237 | project_dict['sysroot'] = '-isysroot' + project_dict['sysroot'] 238 | 239 | if 'name_override' in project_dict: 240 | project_dict['name'] = project_dict['name_override'] 241 | 242 | log.debug("project_dict after processing through generate_vars:" + log.format(project_dict)) 243 | 244 | return project_dict 245 | 246 | def rules_and_build_statements(self) -> (list, list): 247 | """ 248 | Generate build statements and rules for a given variable set. 249 | 250 | Returns rule_list, build_state as extensions for an outline 251 | """ 252 | 253 | # Trivial project types 254 | if self.project_variables['type'] == 'resource-bundle': 255 | return [ 256 | get_generic_rule('bundle'), 257 | get_generic_rule('stage'), 258 | ], [ 259 | Build('bundle', 'bundle', 'build.ninja'), 260 | Build('stage', 'stage', 'build.ninja'), 261 | ] 262 | if self.project_variables['type'] == 'stage': 263 | return [ 264 | get_generic_rule('stage'), 265 | ], [ 266 | Build('stage', 'stage', 'build.ninja'), 267 | ] 268 | 269 | # Only load rules we need 270 | # { file_type : corresponding_rule_name } 271 | FILE_RULES = { 272 | 'c_files': 'c', 273 | 'cxx_files': 'cxx', 274 | 'dlists': None, 275 | 'files': None, 276 | 'logos_files': None, 277 | 'objc_files': 'objc', 278 | 'objcxx_files': 'objcxx', 279 | 'plists': None, 280 | 'swift_files': 'swift', 281 | } 282 | 283 | build_state = [] 284 | rule_list = [] 285 | used_rules = {'debug', 'sign', 'stage', 'lipo'} 286 | subdir: str = self.project_variables['dir'] + '/' 287 | filedict = classify({key: self.project_variables[key] for key in FILE_RULES}) 288 | linker_conds = set() 289 | 290 | # switch to cxx for pp files (auto-links ++ stdlib) 291 | if any("cxx" in ftype for ftype in filedict): 292 | self.project_variables["ld"] = self.project_variables["cxx"] 293 | 294 | # Deal with logos preprocessing 295 | if 'logos_files' in filedict: 296 | for f in standardize_file_list(subdir, filedict['logos_files']): 297 | used_rules.add('logos') 298 | linker_conds.add('-lobjc') 299 | 300 | name, ext = os.path.split(f)[1], os.path.splitext(f)[1] 301 | if ext == '.x': 302 | build_state.append(Build(f'$builddir/logos/{name}.m', 'logos', f)) 303 | filedict.setdefault('objc_files', []) # Create a list here if it doens't exist 304 | filedict['objc_files'].append(f'$builddir/logos/{name}.m') 305 | elif ext == '.xm': 306 | build_state.append(Build(f'$builddir/logos/{name}.mm', 'logos', f)) 307 | filedict.setdefault('objcxx_files', []) 308 | filedict['objcxx_files'].append(f'$builddir/logos/{name}.mm') 309 | # switch to cxx for pp files (auto-links ++ stdlib) 310 | self.project_variables["ld"] = self.project_variables["cxx"] 311 | 312 | # Deal with compilation 313 | archs = self.project_variables['archs'] 314 | if any(x in ['armv6', 'armv7', 'armv7s'] for x in archs): 315 | # https://twitter.com/saurik/status/654198997024796672 316 | # iOS 9+ 32-bit pagesize on 64-bit CPUs changed to 16384 bytes from 4096 317 | linker_conds.add("-Xlinker -segalign -Xlinker 4000") 318 | 319 | for a in archs: 320 | arch_specific_object_files = [] 321 | 322 | for ftype in (f for f in FILE_RULES if FILE_RULES[f] is not None and f in filedict): 323 | ruleid = f'{FILE_RULES[ftype]}' 324 | for f in standardize_file_list(subdir, filedict[ftype]): 325 | # if file type has a rule 326 | if ruleid is not None: 327 | name = os.path.split(f)[1] 328 | rulename = ruleid + f'{a}' 329 | # if rule is not already defined 330 | if not any(r.name == rulename for r in rule_list): 331 | rule_list.append(Rule(rulename, rules(ruleid, 'desc', replace={'{arch}' : f'{a}'}), rules(ruleid, 'cmd', replace={'{arch}' : f'{a}'}))) 332 | arch_specific_object_files.append(f'$builddir/{a}/{name}.o') 333 | build_state.append(Build(f'$builddir/{a}/{name}.o', rulename, f)) 334 | 335 | LINKER_FLAGS = { # Don't link objc if not needed 336 | 'objc': ['-lobjc'], 337 | 'objcxx': ['-lobjc'], 338 | } 339 | 340 | if ftype in LINKER_FLAGS: 341 | for flag in LINKER_FLAGS[ftype]: 342 | linker_conds.add(flag) 343 | 344 | # Linker rules and build statements 345 | cmd = rules('link', 'cmd', replace={'{arch}' : f'{a}'}) + ' ' + ' '.join(linker_conds) 346 | if self.project_variables['type'] == 'static': 347 | cmd = rules('archive', 'cmd') + ' ' + ' '.join(linker_conds) 348 | rule_list.append(Rule(f'link{a}', rules('link', 'desc', replace={'{arch}' : f'{a}'}), cmd)) 349 | build_state.append(Build(f'$builddir/$name.{a}', 350 | f'link{a}', 351 | arch_specific_object_files)) 352 | 353 | build_state.extend([ 354 | # lipo if needed, else use a copy rule to rename it to what the next rule expects 355 | # the copy rule could be optimized out, but its probably more developmentally clear 356 | # to have it there anyways /shrug 357 | Build('$internalsymtarget', 358 | 'lipo' if len(self.project_variables['archs']) > 1 else 'copy', 359 | [f'$builddir/$name.{a}' for a in self.project_variables['archs']]), 360 | # Debug symbols 361 | Build('$internalsigntarget', 'debug', '$internalsymtarget'), 362 | # Codesign 363 | Build('$build_target_file', 'dummy' if self.project_variables['type'] == 'static' else 'sign', '$internalsigntarget'), 364 | # Stage commands (these are actually ran at a different point in the 'runner') 365 | Build('stage', 'stage', 'build.ninja'), 366 | ]) 367 | 368 | # Fix used_rules, TODO: maybe this could be optimized elsewhere? 369 | if len(self.project_variables['archs']) <= 1: 370 | used_rules.remove("lipo") 371 | used_rules.add("copy") 372 | 373 | if self.project_variables['type'] == 'static': 374 | # use a dummy rule to rename it to what the next rule expects 375 | # the dummy rule could be optimized out, but its probably more developmentally clear 376 | # to have it there anyways /shrug 377 | used_rules.remove("sign") 378 | used_rules.add("dummy") 379 | 380 | rule_list.extend(get_generic_rule(r) for r in used_rules) 381 | 382 | return rule_list, build_state 383 | 384 | def generate_ninja_outline(self) -> list: 385 | """ 386 | Generate list of unevaluated build.ninja statements 387 | 388 | Seealso: rules_and_build_statements 389 | """ 390 | 391 | outline = [ 392 | Var('name'), 393 | Var('lowername'), 394 | ___, 395 | Comment(f'Build file for {self.project_variables["name"]}'), 396 | Comment(f'Generated at {datetime.now().strftime("%D %H:%M:%S")}'), 397 | ___, 398 | Var('stagedir'), 399 | Var('location'), 400 | Var('dragon_root_dir'), 401 | Var('sysroot'), 402 | Var('dragon_data_dir'), 403 | Var('objdir'), 404 | Var('signdir'), 405 | Var('builddir'), 406 | Var('build_target_file'), 407 | Var('pwd'), 408 | Var('resource_dir'), 409 | Var('toolchain-prefix'), 410 | ___, 411 | Var('stage'), 412 | Var('stage2'), 413 | ___, 414 | ___, 415 | Var('internalsigntarget'), 416 | Var('internalsymtarget'), 417 | ___, 418 | Var('fwSearch'), 419 | Var('libSearch'), 420 | Var('modulesinternal'), 421 | ___, 422 | Var('cc'), 423 | Var('codesign'), 424 | Var('cxx'), 425 | Var('dsym'), 426 | Var('ld'), 427 | Var('lipo'), 428 | Var('logos'), 429 | Var('optool'), 430 | Var('swift'), 431 | ___, 432 | Var('targetvers'), 433 | Var('targetos'), 434 | Var('triple'), 435 | ___, 436 | Var('frameworks'), 437 | Var('libs'), 438 | ___, 439 | Var('prefix'), 440 | Var('macros'), 441 | Var('arc'), 442 | Var('btarg'), 443 | Var('debug'), 444 | Var('entfile'), 445 | Var('entflag'), 446 | Var('optim'), 447 | Var('warnings'), 448 | ___, 449 | Var('cinclude'), 450 | Var('header_includes'), 451 | Var('public_headers'), 452 | ___, 453 | Var('usrCflags'), # Unused? 454 | Var('usrLDflags'), # Unused? 455 | ___, 456 | Var('libflags'), 457 | Var('lopts'), 458 | Var('typeldflags'), 459 | ___, 460 | Var('cflags'), 461 | Var('ldflags'), 462 | Var('lflags'), 463 | Var('lfflags'), 464 | Var('swiftflags'), 465 | ___, 466 | Var('internalreleaseflags') if _RELEASE_BUILD else Var('internaldbgflags'), 467 | Var('releaseflags') if _RELEASE_BUILD else Var('dbgflags'), 468 | ___, 469 | Var('rootless_prefix'), 470 | Var('rpathflags'), 471 | ___, 472 | Var('theosshim'), 473 | Var('internalcflags'), 474 | Var('internalldflags'), 475 | Var('internallflags'), 476 | Var('internallfflags'), 477 | Var('internalswiftflags'), 478 | ___, 479 | ] 480 | 481 | rule_list, build_state = self.rules_and_build_statements() 482 | 483 | outline.extend(rule_list) 484 | outline.append(___) 485 | outline.extend(build_state) 486 | outline.append(___) 487 | outline.append(Default(['$build_target_file'])) 488 | 489 | return outline 490 | 491 | 492 | def replace_placeholders(data, replace): 493 | if isinstance(data, dict): 494 | return {k: replace_placeholders(v, replace) for k, v in data.items()} 495 | elif isinstance(data, list): 496 | return [replace_placeholders(v, replace) for v in data] 497 | elif isinstance(data, str): 498 | for key, value in replace.items(): 499 | data = data.replace(key, value) 500 | return data 501 | 502 | 503 | def rules(*key_path: str, replace: dict = None) -> dict: 504 | """ 505 | Lazy load default rules and return value specified path. 506 | 507 | Raises: FileNotFoundError, KeyError 508 | """ 509 | 510 | global _LAZY_RULES_DOT_YML 511 | if _LAZY_RULES_DOT_YML is None: 512 | with open(f'{os.environ["DRAGON_ROOT_DIR"]}/internal/rules.yml') as f: 513 | _LAZY_RULES_DOT_YML = yaml.safe_load(f) 514 | 515 | key_path = list(key_path) 516 | ret = _LAZY_RULES_DOT_YML.copy() 517 | while key_path: 518 | ret = ret[key_path.pop(0)] 519 | if replace is not None: 520 | ret = replace_placeholders(ret, replace) 521 | return ret 522 | 523 | 524 | def get_default_section_dict(*key_path: str) -> dict: 525 | """ 526 | Lazy load defaults.yml and return the requested dictionary from it 527 | 528 | Raises: FileNotFoundError, KeyError 529 | """ 530 | 531 | global _LAZY_DEFAULTS_DOT_YML 532 | with open(f'{os.environ["DRAGON_ROOT_DIR"]}/internal/defaults.yml') as f: 533 | _LAZY_DEFAULTS_DOT_YML = yaml.safe_load(f) 534 | with open(f'{os.environ["DRAGON_ROOT_DIR"]}/internal/targets.yml') as f: 535 | _LAZY_DEFAULTS_DOT_YML.update(yaml.safe_load(f)) 536 | with open(f'{os.environ["DRAGON_ROOT_DIR"]}/internal/types.yml') as f: 537 | _LAZY_DEFAULTS_DOT_YML.update(yaml.safe_load(f)) 538 | 539 | key_path = list(key_path) 540 | ret = _LAZY_DEFAULTS_DOT_YML.copy() 541 | while key_path: 542 | ret = ret[key_path.pop(0)] 543 | return ret 544 | 545 | 546 | def handle(ex: Exception): 547 | """ Optionally print debug information """ 548 | 549 | dberror("Dragon Gen", "Press v for detailed debugging output, any other key to exit.") 550 | 551 | try: 552 | old_setting = termios.tcgetattr(sys.stdin.fileno()) 553 | tty.setraw(sys.stdin) 554 | x = sys.stdin.read(1) 555 | termios.tcsetattr(0, termios.TCSADRAIN, old_setting) 556 | if str(x).lower() == 'v': 557 | dberror("Dragon Gen", str(ex)) 558 | dberror("Dragon Gen", ''.join(traceback.format_tb(ex.__traceback__))) 559 | else: 560 | dberror("Dragon Gen", "Exiting...") 561 | except Exception: 562 | dberror("Dragon Gen", f'tty/termios unavailable: {ex}') 563 | dberror("Dragon Gen", "Exiting...") 564 | 565 | print('export DRAGONGEN_FAILURE=1') 566 | 567 | 568 | def main(): 569 | """ 570 | Generate and write build.ninja file from DragonMake or Makefile 571 | 572 | Outline of this method: 573 | - Pull in the actual raw dict from the DragonMake or Makefile 574 | - Process raw data if needed via the Makefile or Legacy (bash) interpreters 575 | - Iterate through the top-level keys in the dictionary 576 | - Call the Generator class to write to {top-level-key-name}.ninja for each 577 | - Pass some export commands to the parent bash script via stdout 578 | """ 579 | META_KEYS = { # Keys that may be at the root of the DragonMake dict 580 | 'name': 'package_name', 581 | 'icmd': 'install_command', 582 | 'ip': 'DRBIP', 583 | 'postinst': None, 584 | 'preinst': None, 585 | 'postrm': None, 586 | 'prerm': None, 587 | 'port': 'DRBPORT', 588 | 'id': None, 589 | 'mtn': None, 590 | 'author': None, 591 | 'version': None, 592 | 'depends': None, 593 | 'conflicts': None, 594 | 'architecture': None, 595 | 'description': None, 596 | 'section': None, 597 | 'pack': None, 598 | 'package': None, 599 | 'desc': None, 600 | 'all': None, 601 | 'icon': None, 602 | 'depiction': None, 603 | 'sldepiction': None, 604 | 'sileodepiction': None 605 | } 606 | 607 | exports = {} 608 | dirs = '' 609 | projs = '' 610 | 611 | if os.path.exists('DragonMake'): 612 | with open('DragonMake') as f: 613 | try: 614 | config = yaml.safe_load(f) 615 | except Exception as ex: 616 | # If the file we tried to load isn't YAML, try running it as a bash script 617 | if os.system("sh DragonMake 2>/dev/null") == 0: 618 | # If that worked, it's the old (OLD) legacy DragonMake format, 619 | # which we can easily support via a couple lines of regex 620 | config = load_old_format(open('DragonMake')) 621 | dbstate("Dragon Gen", "Loading Legacy format DragonMake") 622 | else: 623 | # bad format 624 | dberror("Dragon Gen", "Formatting Error in the DragonMake file") 625 | dberror("Dragon Gen", "Check YAML syntax or file an issue") 626 | dberror("Dragon Gen", 'https://github.com/DragonBuild/dragon') 627 | raise ex 628 | 629 | elif os.path.exists('Makefile'): 630 | from .theos import TheosMakefileProcessor 631 | config = TheosMakefileProcessor().project 632 | # config = interpret_theos_makefile(open('Makefile')) 633 | exports['theos'] = 1 634 | dbstate("Dragon Gen", "Generating build scripts from Theos Makefile") 635 | global _IS_THEOS_MAKEFILE_ 636 | _IS_THEOS_MAKEFILE_ = True 637 | 638 | else: 639 | raise FileNotFoundError 640 | 641 | dbstate("Dragon Gen", "Generating build scripts") 642 | for key in config: 643 | if key in META_KEYS: 644 | continue 645 | 646 | # Hack to run a bash command in the context of DragonGen from a DragonMake file 647 | if key == 'exports': 648 | exports.update(config[key]) 649 | continue 650 | 651 | submodule_config = { 652 | 'name': key, 653 | 'dir': '.' 654 | } 655 | try: 656 | submodule_config.update(config[key]) 657 | except ValueError: 658 | # if i add a key to control.py and don't add it to meta tags here, this happens 659 | # so maybe find a better way to do that, dpkg is complex and has many fields 660 | dbwarn("Dragon Gen", "! Warning: Key %s is not a valid module (a dictionary)," 661 | " nor is it a known configuration key" % key) 662 | dbwarn("Dragon Gen", "! This key will be ignored.") 663 | continue 664 | 665 | default_target = 'ios' 666 | if os.environ['TARG_SIM'] == '1': 667 | default_target = 'sim' 668 | 669 | with open(f'{submodule_config["dir"]}/{submodule_config["name"]}.ninja', 'w+') as out: 670 | try: 671 | generator = Generator(config, key, default_target) 672 | dbstate("Dragon Gen", f"Creating build script for {key}") 673 | generator.write_output_file(out) 674 | except Exception as ex: 675 | dberror("Dragon Gen", f'Exception in module "{key}":') 676 | raise ex 677 | 678 | dirs = dirs + ' ' + submodule_config['dir'] 679 | dirs = dirs.strip() 680 | if dirs.endswith('.'): 681 | dirs = '. ' + dirs[:-2] 682 | 683 | projs = projs + ' ' + submodule_config['name'] 684 | projs = projs.strip() 685 | 686 | exports['project_dirs'] = dirs 687 | exports['project_names'] = projs 688 | 689 | for x in exports: 690 | print(f'export {x}="{exports[x]}"') 691 | 692 | 693 | if __name__ == '__main__': 694 | try: 695 | if os.environ['DGEN_DEBUG']: 696 | log.LOG_LEVEL = LogLevel.DEBUG 697 | if os.environ['RELEASE'] == "1": 698 | _RELEASE_BUILD = True 699 | main() 700 | except FileNotFoundError as exception: 701 | print('Error: No project files found', file=sys.stderr) 702 | 703 | handle(exception) 704 | sys.exit(2) 705 | except KeyError as exception: 706 | print('KeyError: Missing value in variables array.', file=sys.stderr) 707 | print(str(exception), file=sys.stderr) 708 | 709 | handle(exception) 710 | sys.exit(2) 711 | except IndexError as exception: 712 | print("IndexError: List index out of range.", file=sys.stderr) 713 | print(str(exception), file=sys.stderr) 714 | 715 | handle(exception) 716 | sys.exit(2) 717 | except Exception as exception: 718 | print('Error: An undocumented error has been hit', file=sys.stderr) 719 | print('Please contact a maintainer', file=sys.stderr) 720 | 721 | handle(exception) 722 | sys.exit(-1) 723 | -------------------------------------------------------------------------------- /src/dragongen/theos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | from enum import Enum 5 | from ruyaml import YAML 6 | 7 | 8 | def strip_comments(text): 9 | if not text: 10 | return '' 11 | out = text.split('#')[0] 12 | if not out: 13 | return '' 14 | return out 15 | 16 | 17 | def join_escaped_newlines(lines): 18 | out_statements = [] 19 | joining = False 20 | current_statement = "" 21 | for index, line in enumerate(lines): 22 | escaped = False 23 | if line.strip().endswith('\\'): 24 | escaped = True 25 | if joining: 26 | current_statement += ' ' + line 27 | if not escaped: 28 | out_statements.append(current_statement) 29 | current_statement = "" 30 | else: 31 | out_statements.append(line) 32 | if not escaped: 33 | joining = False 34 | return out_statements 35 | 36 | 37 | def join_indented_blocks(lines): 38 | out_statements = [] 39 | joining = False 40 | current_statement = "" 41 | for line in lines: 42 | if line.strip().endswith('::'): 43 | joining = True 44 | current_statement += line + "\n" 45 | continue 46 | 47 | if joining: 48 | if line.startswith('\t') or line.startswith(' '): 49 | current_statement += line + '\n' 50 | else: 51 | joining = False 52 | out_statements.append(current_statement) 53 | current_statement = "" 54 | else: 55 | out_statements.append(line) 56 | if current_statement != "": 57 | out_statements.append(current_statement) 58 | return out_statements 59 | 60 | 61 | class MakefileVariableStatementType(Enum): 62 | DECLARATION = 0 63 | APPEND = 1 64 | 65 | 66 | class MakefileVariableStatement: 67 | def __init__(self, declaration): 68 | 69 | equal_split = declaration.split('=') 70 | 71 | variable_sect = equal_split[0] 72 | value_sect = equal_split[1] 73 | 74 | mod = variable_sect[-1] 75 | if mod in [':', '?', '+']: 76 | if mod == '+': 77 | self.type = MakefileVariableStatementType.APPEND 78 | else: 79 | self.type = MakefileVariableStatementType.DECLARATION 80 | variable_sect = variable_sect[:-1] 81 | else: 82 | self.type = MakefileVariableStatementType.DECLARATION 83 | 84 | self.exported = False 85 | if variable_sect.startswith('export '): 86 | self.exported = True 87 | self.name = variable_sect.split('export ', 1)[1] 88 | else: 89 | self.name = variable_sect 90 | 91 | self.value = value_sect 92 | self.name = self.name.strip() 93 | self.value = self.value.strip() 94 | 95 | 96 | class Makefile: 97 | def __init__(self, file_contents: str): 98 | self.includes = [] 99 | self.variables = {} 100 | lines = [strip_comments(line) for line in file_contents.split('\n')] 101 | self.statements = [line for line in join_indented_blocks(join_escaped_newlines(lines)) if line != ''] 102 | self.variable_statements = [] 103 | self.rules = {} 104 | 105 | for statement in self.statements: 106 | 107 | if statement.startswith('include '): 108 | self.includes.append(statement.split('include ')[1]) 109 | 110 | elif '=' in statement: 111 | self.variable_statements.append(MakefileVariableStatement(statement)) 112 | 113 | elif '::\n' in statement: 114 | self.rules[statement.split("::\n")[0]] = [i.strip() for i in statement.split('\n')[1:] if 115 | i.strip() != ""] 116 | 117 | self.variables = self._process_variable_statements() 118 | 119 | def _process_variable_statements(self): 120 | variables = {} 121 | for statement in self.variable_statements: 122 | if statement.type == MakefileVariableStatementType.DECLARATION: 123 | variables[statement.name] = " ".join(statement.value.split()) 124 | elif statement.type == MakefileVariableStatementType.APPEND: 125 | if statement.name in variables: 126 | variables[statement.name] = variables[statement.name] + ' ' + " ".join(statement.value.split()) 127 | else: 128 | variables[statement.name] = " ".join(statement.value.split()) 129 | return variables 130 | 131 | 132 | class TheosMakefileType(Enum): 133 | TWEAK = 0 134 | BUNDLE = 1 135 | LIBRARY = 2 136 | APPLICATION = 3 137 | FRAMEWORK = 4 138 | TOOL = 5 139 | PREFS = 6 140 | 141 | 142 | class TheosMakefile(Makefile): 143 | def __init__(self, file_contents: str): 144 | super().__init__(file_contents) 145 | 146 | self.type = None 147 | self.has_subprojects = False 148 | self.module_name = "" 149 | self.module = {} 150 | self.subprojects = [] 151 | 152 | self.module['arc'] = False 153 | self.module['for'] = 'ios' 154 | 155 | for included in self.includes: 156 | if 'tweak.mk' in included: 157 | self.type = TheosMakefileType.TWEAK 158 | self.module['frameworks'] = ['UIKit'] # :/ 159 | self.module['type'] = 'tweak' 160 | if 'aggregate.mk' in included: 161 | self.has_subprojects = True 162 | if 'bundle.mk' in included: 163 | self.module['type'] = 'bundle' 164 | self.type = TheosMakefileType.BUNDLE 165 | if 'library.mk' in included: 166 | self.module['type'] = 'library' 167 | self.type = TheosMakefileType.LIBRARY 168 | if 'application.mk' in included: 169 | self.module['type'] = 'app' 170 | self.type = TheosMakefileType.APPLICATION 171 | if 'framework.mk' in included: 172 | self.module['type'] = 'framework' 173 | self.type = TheosMakefileType.FRAMEWORK 174 | if 'tool.mk' in included: 175 | self.module['type'] = 'tool' 176 | self.type = TheosMakefileType.TOOL 177 | 178 | for variable in self.variables: 179 | self.variables[variable] = self.variables[variable].replace('$(THEOS_STAGING_DIR)', '$dragon_data_dir/_') 180 | self.variables[variable] = self.variables[variable].replace('$(THEOS)', '$dragon_root_dir') 181 | self.variables[variable] = self.variables[variable].replace('$(ECHO_NOTHING)', '') 182 | self.variables[variable] = self.variables[variable].replace('$(ECHO_END)', '') 183 | self.variables[variable] = self.variables[variable].replace('$(', '$$(') 184 | 185 | # TODO: '_SWIFTFLAGS' 186 | suffix_to_key = { 187 | '_ARCHS': 'archs', 188 | '_FILES': 'files', 189 | '_FRAMEWORKS': 'frameworks', 190 | '_LIBRARIES': 'libs', 191 | '_CFLAGS': 'cflags', 192 | '_CXXFLAGS': 'cxxflags', 193 | '_CCFLAGS': 'cxxflags', 194 | '_LDFLAGS': 'ldflags', 195 | '_CODESIGN_FLAGS': 'entflag', 196 | '_INSTALL_PATH': 'install_location', 197 | '_LINKAGE_TYPE': 'type', 198 | '_RESOURCE_DIRS': 'resource_dir', 199 | '_PUBLIC_HEADERS': 'public_headers' 200 | } 201 | 202 | for suffix, key in suffix_to_key.items(): 203 | if variable.endswith(suffix): 204 | if key in self.module and isinstance(self.module[key], list): 205 | self.module[key] += self.variables[variable].split(' ') 206 | else: 207 | lists = ['_ARCHS', '_FILES', '_FRAMEWORKS', '_LIBRARIES'] 208 | self.module[key] = self.variables[variable].split(' ') if suffix in lists else self.variables[variable] 209 | 210 | # Handle special cases 211 | if variable.endswith('_NAME'): 212 | self.module_name = self.variables[variable] 213 | 214 | if variable.endswith('_CFLAGS') and '-fobjc-arc' in self.variables[variable]: 215 | self.module['arc'] = True 216 | 217 | if variable.endswith('_INSTALL_PATH') and 'PreferenceBundles' in self.variables[variable]: 218 | self.module['type'] = 'prefs' 219 | self.type = TheosMakefileType.PREFS 220 | 221 | if variable.endswith('_LINKAGE_TYPE') and self.variables[variable] == 'static': 222 | self.module['type'] = 'static' 223 | 224 | if variable == 'TARGET' or variable == 'TARGET_OS_DEPLOYMENT_VERSION': 225 | ver = self.variables[variable].split(':')[-1] if variable == 'TARGET' else self.variables[variable] 226 | if float(ver) >= 9.0: 227 | self.module['targetvers'] = ver 228 | 229 | if variable == 'SYSROOT': 230 | sysroot = self.variables[variable] 231 | if os.path.exists(sysroot): 232 | self.module['sysroot'] = sysroot 233 | 234 | files = [] 235 | if 'files' in self.module: 236 | tokens = self.module['files'] 237 | nextisawildcard = False 238 | for i in tokens: 239 | if '$(wildcard' in i: 240 | nextisawildcard = 1 241 | continue 242 | if nextisawildcard: 243 | # We dont want to stop with these til we hit a ')' 244 | # thanks cr4shed ._. 245 | nextisawildcard = 0 if ')' in i else 1 246 | grab = i.split(')')[0] 247 | files.append(grab.replace(')', '')) 248 | continue 249 | files.append(i) 250 | 251 | self.module['files'] = files 252 | 253 | if 'stage' in self.rules: 254 | stage = self.rules['stage'] 255 | stage_processed = [] 256 | for command in stage: 257 | command = command.replace('$(THEOS_STAGING_DIR)', '$dragon_data_dir/_') 258 | command = command.replace('$(THEOS)', '$dragon_root_dir') 259 | command = command.replace('$(ECHO_NOTHING)', '') 260 | command = command.replace('$(ECHO_END)', '') 261 | command = command.replace('$(', '$$(') 262 | stage_processed.append(command) 263 | self.module['stage'] = stage_processed 264 | 265 | if 'files' not in self.module: 266 | if self.type == TheosMakefileType.BUNDLE: 267 | self.module['type'] = 'resource-bundle' 268 | 269 | if self.has_subprojects: 270 | self.subprojects = self.variables['SUBPROJECTS'].split(' ') 271 | 272 | 273 | class TheosMakefileProcessor: 274 | def __init__(self): 275 | self.root_directory = os.getcwd() 276 | self.project = {} 277 | 278 | with open('Makefile', 'r') as makefile: 279 | self.root_makefile = TheosMakefile(makefile.read()) 280 | 281 | if os.path.exists('control'): 282 | with open('control', 'r') as control: 283 | yaml = YAML(typ='safe') # default, if not specfied, is 'rt' (round-trip) 284 | self.control = yaml.load(control) 285 | elif os.path.exists('layout/DEBIAN/control'): 286 | with open('layout/DEBIAN/control', 'r') as control: 287 | yaml = YAML(typ='safe') 288 | self.control = yaml.load(control) 289 | 290 | self.project['name'] = self.control['Name'] 291 | 292 | # TODO: this does nothing atm since Theos projects don't generater DragonMakes 293 | # which are queried in bin/dragon for icmd else 'sbreload' 294 | if 'INSTALL_TARGET_PROCESSES' in self.root_makefile.variables: 295 | self.project['icmd'] = 'killall -9 ' + self.root_makefile.variables['INSTALL_TARGET_PROCESSES'] 296 | else: 297 | self.project['icmd'] = 'sbreload' 298 | 299 | self._process_makefile(self.root_makefile) 300 | # print(self.project, file=sys.stderr) 301 | 302 | def _process_makefile(self, makefile): 303 | if makefile.type: 304 | self.project[makefile.module_name] = makefile.module 305 | if makefile.has_subprojects: 306 | for subproject_name in makefile.subprojects: 307 | this_cwd = os.getcwd() 308 | os.chdir(subproject_name) 309 | with open('Makefile', 'r') as subproject_makefile_file: 310 | subproject_makefile = TheosMakefile(subproject_makefile_file.read()) 311 | subproject_makefile.module['dir'] = os.getcwd().replace(self.root_directory, '')[1:] 312 | self._process_makefile(subproject_makefile) 313 | os.chdir(this_cwd) 314 | -------------------------------------------------------------------------------- /src/dragongen/toolchain.py: -------------------------------------------------------------------------------- 1 | import os 2 | from shared.util import system_with_output 3 | 4 | class Toolchain: 5 | def __init__(self): 6 | self.clang = "clang" 7 | self.clangpp = "clang++" 8 | self.ass = self.clang 9 | self.ld = self.clang 10 | self.codesign = "ldid" 11 | self.dsym = "dsymutil" 12 | self.plutil = "plutil" 13 | self.lipo = "lipo" 14 | self.tapi = "tapi" 15 | 16 | @classmethod 17 | def locate_macos_toolchain(cls, use_objcs: bool): 18 | tc_dir = "" 19 | if use_objcs: 20 | tc_dir = os.environ['DRAGON_ROOT_DIR'] + '/llvm-objcs/bin/' 21 | else: 22 | stat, xcrun_out, _ = system_with_output('xcrun -f clang') 23 | tc_dir = "" 24 | 25 | if stat == 0: 26 | tc_dir = os.path.dirname(xcrun_out) + '/' 27 | elif os.path.exists('/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/'): 28 | tc_dir = '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/' 29 | elif system_with_output('command -v clang')[0] == 0: 30 | pass 31 | else: 32 | return None 33 | 34 | tc = cls() 35 | tc.clang = tc_dir + 'clang' 36 | tc.clangpp = tc_dir + 'clang++' 37 | tc.ass = tc.clang 38 | tc.ld = tc.clang 39 | tc.dsym = tc_dir + 'dsymutil' 40 | # FIXME: hardcoded while I wait on a real distribution of llvm-objcs 41 | if use_objcs: 42 | tc.lipo = '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo' 43 | else: 44 | tc.lipo = tc_dir + 'lipo' 45 | tc.tapi = tc_dir + 'tapi' 46 | 47 | return tc 48 | 49 | @classmethod 50 | def locate_linux_toolchain(cls, use_objcs: bool): 51 | tc_dir = "" 52 | if use_objcs: 53 | tc_dir = os.environ['DRAGON_ROOT_DIR'] + '/llvm-objcs/bin/' 54 | elif os.path.exists(os.environ['DRAGON_ROOT_DIR'] + '/toolchain/linux/iphone/bin/'): 55 | tc_dir = os.environ['DRAGON_ROOT_DIR'] + '/toolchain/linux/iphone/bin/' 56 | elif system_with_output('command -v clang')[0] == 0: 57 | pass 58 | else: 59 | return None 60 | 61 | tc = cls() 62 | tc.clang = tc_dir + 'clang' 63 | tc.clangpp = tc_dir + 'clang++' 64 | tc.ass = tc.clang 65 | tc.ld = tc.clang 66 | tc.codesign = tc_dir + 'ldid' 67 | tc.dsym = tc_dir + 'dsymutil' 68 | tc.lipo = tc_dir + 'lipo' 69 | tc.tapi = tc_dir + 'tapi' 70 | 71 | return tc 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/dragongen/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os, sys, glob, inspect 4 | import re as regex 5 | from enum import Enum 6 | from pprint import pprint, pformat 7 | from .variable_types import ArgList 8 | 9 | 10 | make_match = regex.compile('(.*)=(.*)#?') 11 | make_type = regex.compile(r'include.*\/(.*)\.mk') 12 | nepmatch = regex.compile(r'(.*)\+=(.*)#?') # nep used subproj += instead of w/e and everyone copies her. 13 | 14 | 15 | def classify(filedict: dict) -> dict: 16 | ''' 17 | Find loving homes for unclassified files 18 | ''' 19 | 20 | for f in filedict['files']: 21 | _, ext = os.path.splitext(f) 22 | filedict[{ 23 | '.c': 'c_files', 24 | '.cpp': 'cxx_files', 25 | '.cxx': 'cxx_files', 26 | '.dlist': 'dlists', 27 | '.m': 'objc_files', 28 | '.mm': 'objcxx_files', 29 | '.plist': 'plists', 30 | '.swift': 'swift_files', 31 | '.x': 'logos_files', 32 | '.xm': 'logos_files', 33 | }[ext]].append(f) 34 | # Clear out empty keys 35 | return {key: value for (key, value) in filedict.items() if value != []} 36 | 37 | 38 | # NOTE: currently unused; see src/dragongen/generation.py's main() 39 | # this was supposed to be a really small function, i dont know what happened ;-; 40 | def interpret_theos_makefile(file: object, root: object = True) -> dict: 41 | project = {} 42 | variables = {} 43 | stage = [] 44 | stageactive = False 45 | module_type = '' 46 | arc = False 47 | hassubproj = False 48 | noprefix = False 49 | try: 50 | while 1: 51 | line = file.readline().split('#')[0] 52 | if not line: 53 | break 54 | if not arc and '-fobjc-arc' in line: 55 | arc = True 56 | if not noprefix and '-DTHEOS_LEAN_AND_MEAN' in line: 57 | noprefix = True 58 | if line == 'internal-stage::': 59 | stageactive = True 60 | continue 61 | if stageactive: 62 | if line.startswith((' ', '\t')): 63 | x = line 64 | x = x.replace('$(THEOS_STAGING_DIR)', '$dragon_data_dir/_') 65 | x = x.replace('$(ECHO_NOTHING)', '') 66 | x = x.replace('$(ECHO_END)', '') 67 | stage.append(x) 68 | else: 69 | stageactive = False 70 | 71 | if not make_match.match(line): 72 | if not make_type.match(line): 73 | continue 74 | if 'aggregate' in make_type.match(line).group(1): 75 | hassubproj = True 76 | else: 77 | module_type = make_type.match(line).group(1) 78 | continue 79 | 80 | if not nepmatch.match(line): 81 | name, value = make_match.match(line).group(1, 2) 82 | else: 83 | name, value = nepmatch.match(line).group(1, 2) 84 | if name.strip() in variables: 85 | variables[name.strip()] = variables[name.strip()] + ' ' + value.strip() 86 | else: 87 | variables[name.strip()] = value.strip() 88 | finally: 89 | file.close() 90 | 91 | if root: 92 | project['name'] = os.path.basename(os.getcwd()) 93 | if 'INSTALL_TARGET_PROCESS' in variables: 94 | project['icmd'] = 'killall -9 ' + variables['INSTALL_TARGET_PROCESS'] 95 | else: 96 | project['icmd'] = 'sbreload' 97 | 98 | if os.environ['DGEN_DEBUG']: 99 | print("\n\n", file=sys.stderr) 100 | print("module type:" + str(module_type), file=sys.stderr) 101 | print("\n\n", file=sys.stderr) 102 | 103 | modules = [] 104 | mod_dicts = [] 105 | # if module_type == 'aggregate': 106 | 107 | module_type_naming = module_type.upper() 108 | 109 | module_name = variables.get(f'{module_type_naming}_NAME') 110 | if module_name: 111 | module_archs = variables.get('ARCHS') 112 | module_files = variables.get(module_name + '_FILES') or variables.get( 113 | f'$({module_type_naming}_NAME)_FILES') or '' 114 | module_cflags = variables.get(module_name + '_CFLAGS') or variables.get( 115 | '$({module_type_naming}_NAME)_CFLAGS') or '' 116 | module_cxxflags = variables.get(module_name + '_CXXFLAGS') or variables.get( 117 | '$({module_type_naming}_NAME)_CXXFLAGS') or '' 118 | module_cflags = variables.get('ADDITIONAL_CFLAGS') or '' 119 | module_ldflags = variables.get(module_name + '_LDFLAGS') or variables.get( 120 | f'$({module_type_naming}_NAME)_LDFLAGS') or '' 121 | module_codesign_flags = variables.get(module_name + '_CODESIGN_FLAGS') or variables.get( # TODO 122 | f'$({module_type_naming}_NAME)_CODESIGN_FLAGS') or '' 123 | module_frameworks = variables.get(module_name + '_FRAMEWORKS') or variables.get( 124 | f'$({module_type_naming}_NAME)_FRAMEWORKS') or '' 125 | module_pframeworks = variables.get(module_name + '_PRIVATE_FRAMEWORKS') or variables.get( 126 | f'$({module_type_naming}_NAME)_PRIVATE_FRAMEWORKS') or '' 127 | module_eframeworks = variables.get(module_name + '_EXTRA_FRAMEWORKS') or variables.get( 128 | f'$({module_type_naming}_NAME)_EXTRA_FRAMEWORKS') or '' 129 | module_libraries = variables.get(module_name + '_LIBRARIES') or variables.get( 130 | f'$({module_type_naming}_NAME)_LIBRARIES') or '' 131 | module_install_location = variables.get(module_name + '_INSTALL_PATH') or variables.get( 132 | f'$({module_type_naming}_NAME)_INSTALL_PATH') or '' 133 | 134 | files = [] 135 | if module_files: 136 | tokens = module_files.split(' ') 137 | nextisawildcard = False 138 | for i in tokens: 139 | if '$(wildcard' in i: 140 | nextisawildcard = 1 141 | continue 142 | if nextisawildcard: 143 | # We dont want to stop with these til we hit a ')' 144 | # thanks cr4shed ._. (x2) 145 | nextisawildcard = 0 if ')' in i else 1 146 | grab = i.split(')')[0] 147 | files.append(grab.replace(')', '')) 148 | continue 149 | files.append(i) 150 | 151 | module = { 152 | 'type': module_type, 153 | 'files': files 154 | } 155 | if module_name != '': 156 | module['name'] = module_name 157 | module['frameworks'] = [] 158 | if module_frameworks != '': 159 | module['frameworks'] += module_frameworks.split(' ') 160 | if module_pframeworks != '': 161 | module['frameworks'] += module_pframeworks.split(' ') 162 | if module_eframeworks != '': 163 | module['frameworks'] += module_eframeworks.split(' ') 164 | if module_libraries != '': 165 | module['libs'] = module_libraries.split(' ') 166 | if module_archs != '': 167 | module['archs'] = module_archs 168 | else: 169 | module['archs'] = ['arm64', 'arm64e'] 170 | if module_cflags != '': 171 | module['cflags'] = module_cflags 172 | if module_cxxflags: 173 | module['cxxflags'] = module_cxxflags 174 | if module_ldflags: 175 | module['ldflags'] = module_ldflags 176 | if module_install_location != '': 177 | module['install_location'] = module_install_location 178 | if stage != []: 179 | module['stage'] = stage 180 | 181 | module['arc'] = arc 182 | if not root: 183 | return module 184 | else: 185 | mod_dicts.append(module) 186 | project['name'] = module['name'] 187 | modules.append('.') 188 | 189 | if hassubproj and 'SUBPROJECTS' in variables: 190 | modules = modules + variables['SUBPROJECTS'].split(' ') 191 | 192 | rename_counter = 1 193 | for module in modules: 194 | if os.environ['DGEN_DEBUG']: 195 | print("\n\n", file=sys.stderr) 196 | pprint("modules:" + str(modules), stream=sys.stderr) 197 | print("\n\n", file=sys.stderr) 198 | if module != '.' and os.path.exists(module + '/Makefile'): 199 | new = interpret_theos_makefile(open(module + '/Makefile'), root=False) 200 | if new['name'].lower() == project['name'].lower(): 201 | rename_counter += 1 202 | new['name_override'] = new['name'] 203 | new['name'] = new['name'] + str(rename_counter) 204 | mod_dicts.append(new) 205 | 206 | i = 0 207 | for mod in mod_dicts: 208 | if mod: 209 | project[mod['name']] = mod 210 | project[mod['name']]['dir'] = modules[i] 211 | i += 1 212 | 213 | # the magic of theos 214 | if 'export ARCHS' in variables: 215 | project['all'] = { 216 | 'archs': variables['export ARCHS'].split(' ') 217 | } 218 | if 'ARCHS' in variables: 219 | project['all'] = { 220 | 'archs': variables['ARCHS'].split(' ') 221 | } 222 | 223 | if os.environ['DGEN_DEBUG']: 224 | print("\n\n", file=sys.stderr) 225 | print("dict:" + str(project), file=sys.stderr) 226 | print("\n\n", file=sys.stderr) 227 | return project 228 | 229 | 230 | def load_old_format(file: object, root: object = True) -> dict: 231 | variables = {i.split('=')[0].strip('"').strip("'"): i.split('=')[1].strip('"').strip("'") for i in 232 | file.read().split('\n') if (not i.startswith('#') and len(i) > 0)} 233 | # print(variables, file=sys.stderr) 234 | 235 | moddict = {} 236 | 237 | for i in [x for x in variables if len(x) > 0 and x != 'SUBPROJECTS']: 238 | translation = i.lower().replace('tweak_', '').replace('logos_file', 'logos_files').replace('install_cmd', 239 | 'icmd') 240 | variables[i] = os.popen(f'echo {variables[i]}').read().strip() 241 | if i in ['ARCHS', 'LIBS', 'FRAMEWORKS', 'LOGOS_FILES', 'TWEAK_FILES']: 242 | 243 | start, delim = ArgList.LIST_KEYS[i.lower()] 244 | if delim in variables[i]: 245 | moddict[translation] = variables[i][len(start):].split(delim) 246 | else: 247 | moddict[translation] = variables[i].strip().split() 248 | else: 249 | moddict[translation] = variables[i] 250 | 251 | if not root: 252 | return moddict 253 | else: 254 | mainproj = {k: v for (k, v) in moddict.items() if k not in ['name', 'icmd']} 255 | moddict = {k: v for (k, v) in moddict.items() if k in ['name', 'icmd']} 256 | moddict[moddict['name']] = mainproj 257 | for i in variables['SUBPROJECTS'].split(): 258 | os.chdir(i) 259 | subproject = load_old_format(open('DragonMake'), False) 260 | subproject['dir'] = subproject['name'] 261 | os.chdir('..') 262 | moddict[subproject['name']] = {i: v for (i, v) in subproject.items() if i not in ['name', 'icmd']} 263 | return moddict 264 | 265 | 266 | def standardize_file_list(subdir: str, files: list) -> list: 267 | '''Strip list of empty strings and evaluate globbed paths.''' 268 | 269 | # hotfix for #67, standardize the subdirectory string we get, we want /subdir/ 270 | subdir = subdir.strip('/') 271 | subdir = f'/{subdir}/' 272 | 273 | ret = [] 274 | for filename in files: 275 | if not filename: 276 | continue 277 | 278 | if '*' in filename: 279 | ret.extend('./' + f[len(subdir):] for f in glob.glob('.' + subdir + filename, 280 | recursive=True)) 281 | continue 282 | 283 | ret.append(filename) 284 | return ret 285 | 286 | 287 | class LogLevel(Enum): 288 | NONE = -1 289 | ERROR = 0 290 | WARN = 1 291 | INFO = 2 292 | DEBUG = 3 293 | 294 | 295 | class log: 296 | """ 297 | Python's default logging library is absolute garbage 298 | 299 | so we use this. 300 | """ 301 | LOG_LEVEL = LogLevel.ERROR 302 | 303 | @staticmethod 304 | def line(): 305 | return 'dragon.' + os.path.basename(inspect.stack()[2][1]).split('.')[0] + ":" + str(inspect.stack()[2][2]) \ 306 | + ":" + inspect.stack()[2][3] + '()' 307 | 308 | @staticmethod 309 | def format(ob): 310 | return pformat(ob, indent=4) 311 | 312 | @staticmethod 313 | def debug(msg: str): 314 | if log.LOG_LEVEL.value >= LogLevel.DEBUG.value: 315 | print(f'DEBUG - {log.line()} - {msg}', file=sys.stderr) 316 | 317 | @staticmethod 318 | def info(msg: str): 319 | if log.LOG_LEVEL.value >= LogLevel.INFO.value: 320 | print(f'INFO - {log.line()} - {msg}', file=sys.stderr) 321 | 322 | @staticmethod 323 | def warn(msg: str): 324 | if log.LOG_LEVEL.value >= LogLevel.WARN.value: 325 | print(f'WARN - {log.line()} - {msg}', file=sys.stderr) 326 | 327 | @staticmethod 328 | def warning(msg: str): 329 | if log.LOG_LEVEL.value >= LogLevel.WARN.value: 330 | print(f'WARN - {log.line()} - {msg}', file=sys.stderr) 331 | 332 | @staticmethod 333 | def error(msg: str): 334 | if log.LOG_LEVEL.value >= LogLevel.ERROR.value: 335 | print(f'ERROR - {log.line()} - {msg}', file=sys.stderr) 336 | -------------------------------------------------------------------------------- /src/dragongen/variable_types.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | class ArgList(list): 4 | ''' 5 | Variables with values of type list: their corresponding delims and prefixes 6 | ''' 7 | 8 | # This LIST_KEYS logic needs to be shortened, it's horribly repetitive 9 | # As it's now in a separate file as well which is problematic. 10 | # Maybe it can be moved into defaults.py? 11 | LIST_KEYS = { 12 | 'files': ('', ' '), 13 | 'logos_files': ('', ' '), 14 | 'tweak_files': ('', ' '), # used for legacy compatibility, isn't actually used. 15 | 'archs': ('', '-arch '), # Also only for legacy, this is handled in a much more complex manner # TODO: BUGGED 16 | 'c_files': ('', ' '), 17 | 'objc_files': ('', ' '), 18 | 'objcxx_files': ('', ' '), 19 | 'cxx_files': ('', ' '), 20 | 'plists': ('', ' '), 21 | 'swift_files': ('', ' '), 22 | 'dlists': ('', ' '), 23 | 'cflags': ('', ' '), 24 | 'ldflags': ('', ' '), 25 | 'codesignflags': ('', ' '), 26 | 'include': ('-I', ' -I'), 27 | 'header_includes': ('-I', ' -I'), 28 | 'macros': ('-D', ' -D'), 29 | 'prefix': ('-include', ' -include'), 30 | 'fw_dirs': ('-F', ' -F'), 31 | 'additional_fw_dirs': ('-F', ' -F'), 32 | 'fwSearch': ('-F', ' -F'), 33 | 'libSearch': ('-L', ' -L'), 34 | 'lib_dirs': ('-L', ' -L'), 35 | 'additional_lib_dirs': ('-L', ' -L'), 36 | 'libs': ('-l', ' -l'), 37 | 'frameworks': ('-framework ', ' -framework '), 38 | 'stage': ('', '; '), 39 | 'stage2': ('', '; '), 40 | 'lopts': ('', ' '), 41 | 'public_headers': ('', ''), 42 | } 43 | 44 | def __init__(self, values: list, prefix: str = '', delim: str = ' '): 45 | super().__init__(values) 46 | self.delim = delim 47 | self.prefix = prefix 48 | 49 | def __str__(self): 50 | if self.prefix + self.delim.join(str(s) for s in self) == "None": 51 | return "" 52 | if self.prefix + self.delim.join(str(s) for s in self): 53 | return self.prefix + self.delim.join(str(s) for s in self) 54 | return "" 55 | 56 | 57 | # Apparently this isn't used? 58 | class BoolFlag: 59 | ''' 60 | Variables with values of type bool, and their corresponding flag pairs. 61 | ''' 62 | 63 | BOOL_KEYS = { 64 | 'arc': ('-fobjc-arc', ''), 65 | } 66 | 67 | def __init__(self, value: bool, flagpair: (str, str)): 68 | self.value = value 69 | self.true_flag, self.false_flag = flagpair 70 | 71 | def __bool__(self): 72 | return self.value 73 | 74 | def __str__(self): 75 | return self.true_flag if self.value else self.false_flag 76 | 77 | 78 | class ProjectVars(dict): 79 | ''' 80 | Safe dictionary with default values based on keys 81 | ''' 82 | 83 | def __getitem__(self, key): 84 | try: 85 | ret = dict.__getitem__(self, key) 86 | if isinstance(ret, list) and key in ArgList.LIST_KEYS and ArgList(ret, *(ArgList.LIST_KEYS[key])) != []: 87 | return ArgList(ret, *(ArgList.LIST_KEYS[key])) 88 | if isinstance(ret, bool) and key in BoolFlag.BOOL_KEYS: 89 | return BoolFlag(ret, BoolFlag.BOOL_KEYS[key]) 90 | if isinstance(ret, list) and len(ret) == 0: 91 | return '' 92 | return ret 93 | except KeyError as ex: 94 | if key in ['test']: 95 | raise ex 96 | return ArgList([]) if key in ArgList.LIST_KEYS else '' 97 | -------------------------------------------------------------------------------- /src/shared/util.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | import sys 3 | import subprocess 4 | 5 | 6 | class OutputColors(IntEnum): 7 | Red = 31 8 | Green = 32 9 | Yellow = 33 10 | Blue = 34 11 | Cyan = 36 12 | White = 37 13 | Reset = 0 14 | 15 | class OutputWeight(IntEnum): 16 | Normal = 0 17 | Bold = 1 18 | 19 | def color_string(output_color, output_weight): 20 | return f'\033[{output_weight.value};{output_color.value}m' 21 | 22 | def dprintline(label_color, tool_name, text_color, text_weight, pusher, msg): 23 | print(f"{color_string(label_color, OutputWeight.Bold)}[{tool_name}] {color_string(text_color, text_weight)}{'>>> ' if pusher else ''}{msg}{color_string(OutputColors.Reset, OutputWeight.Normal)}", file=sys.stderr) 24 | 25 | def dbstate(tool_name, msg): 26 | dprintline(OutputColors.Green, tool_name, OutputColors.White, OutputWeight.Bold, False, msg) 27 | 28 | def dbwarn(tool_name, msg): 29 | dprintline(OutputColors.Yellow, tool_name, OutputColors.White, OutputWeight.Normal, False, msg) 30 | 31 | def dberror(tool_name, msg): 32 | dprintline(OutputColors.Red, tool_name, OutputColors.White, OutputWeight.Bold, False, msg) 33 | 34 | 35 | def system(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE): 36 | proc = subprocess.Popen("" + cmd, 37 | stdout=stdout, 38 | stderr=stderr, 39 | shell=True, 40 | universal_newlines=True) 41 | std_out, std_err = proc.communicate() 42 | # print(proc.returncode) 43 | return proc.returncode # , std_out, std_err 44 | 45 | 46 | def system_with_output(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE): 47 | proc = subprocess.Popen("" + cmd, 48 | stdout=stdout, 49 | stderr=stderr, 50 | shell=True, 51 | universal_newlines=True) 52 | std_out, std_err = proc.communicate() 53 | return proc.returncode, std_out, std_err 54 | 55 | 56 | def system_pipe_output(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE): 57 | process = subprocess.Popen(cmd, 58 | stdout=stdout, 59 | stderr=stderr, 60 | shell=True, 61 | universal_newlines=True) 62 | 63 | while True: 64 | realtime_output = process.stdout.readline() 65 | realtime_err = process.stderr.readline() 66 | 67 | if realtime_output == '' and realtime_err == '' and process.poll() is not None: 68 | break 69 | 70 | if realtime_output: 71 | print(realtime_output.strip(), flush=True) 72 | if realtime_err: 73 | print(realtime_err.strip(), flush=True, file=sys.stderr) 74 | --------------------------------------------------------------------------------