├── .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 |
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 |
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 |
--------------------------------------------------------------------------------