├── uberpoet
├── resources
│ ├── tools
│ │ └── rules
│ │ │ ├── BUILD.bazel
│ │ │ └── repository_rules.bzl
│ ├── get_market_name.sh
│ ├── mockbuckconfig
│ ├── cpu_log.sh
│ ├── mockpodfile
│ ├── mockbucklibtemplate.bzl
│ ├── mockbazellibtemplate.bzl
│ ├── mockbuckapptemplate.bzl
│ ├── mockcplibtemplate.podspec
│ ├── mockcpapptemplate.podspec
│ ├── Info.plist
│ ├── mockbazelapptemplate.bzl
│ ├── mockappdelegate
│ └── mockbazelworkspace
├── __init__.py
├── memoize.py
├── locreader.py
├── loccalc.py
├── util.py
├── moduletree.py
├── cpulogger.py
├── statemanagement.py
├── genproj.py
├── commandlineutil.py
├── blazeprojectgen.py
├── cpprojectgen.py
└── filegen.py
├── SECURITY.md
├── docs
├── images
│ ├── flat.png
│ ├── bs_flat.png
│ ├── layered_3.png
│ ├── project_gen.png
│ ├── bs_layered_simple.png
│ ├── note.txt
│ └── project_gen.xml
├── layer_types.md
└── CONTRIBUTING.md
├── release_notes.md
├── Pipfile
├── setup.cfg
├── tests
├── fixtures
│ ├── cloc_out.json
│ ├── cpu_log.txt
│ └── loc_mappings.json
├── __init__.py
├── test_dotreader.py
├── utils.py
├── test_locreader.py
├── test_loccalc.py
├── test_cpulogger.py
├── test_moduletree.py
├── test_filegen.py
├── test_statemanagement.py
├── test_buck_integration.py
├── test_cp_integration.py
└── test_bazel_integration.py
├── genproj.py
├── multisuite.py
├── examples
├── buckgenproj_test.sh
├── bazelgenproj_test.sh
├── cpgenproj_test.sh
└── multisuite_test.sh
├── .github
└── workflows
│ └── python-app.yml
├── setup.py
├── .gitignore
├── README.md
└── LICENSE.txt
/uberpoet/resources/tools/rules/BUILD.bazel:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | Just file an issue in github.
4 |
--------------------------------------------------------------------------------
/docs/images/flat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uber/uber-poet/HEAD/docs/images/flat.png
--------------------------------------------------------------------------------
/docs/images/bs_flat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uber/uber-poet/HEAD/docs/images/bs_flat.png
--------------------------------------------------------------------------------
/release_notes.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | ## 0.9.0
4 |
5 | * Initial pre-public release version
--------------------------------------------------------------------------------
/docs/images/layered_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uber/uber-poet/HEAD/docs/images/layered_3.png
--------------------------------------------------------------------------------
/docs/images/project_gen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uber/uber-poet/HEAD/docs/images/project_gen.png
--------------------------------------------------------------------------------
/docs/images/bs_layered_simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uber/uber-poet/HEAD/docs/images/bs_layered_simple.png
--------------------------------------------------------------------------------
/docs/images/note.txt:
--------------------------------------------------------------------------------
1 | The xml file(s) are the file format of graphs created with https://www.draw.io/ . Go there to edit these graphs, export them as PNG files and put the updated files in here.
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 | [packages]
7 | typing = "==3.6.6"
8 | toposort = "==1.6.0"
9 |
10 | [dev-packages]
11 | mock = "==2.0.0"
12 | testfixtures = "==6.3.0"
13 | pytest = "==4.0.0"
14 | pytest-cov = "==2.6.0"
15 | "flake8" = "==3.6.0"
16 | yapf = "==0.25.0"
17 | isort = "==4.3.4"
18 | future = "*"
19 |
20 | [requires]
21 | python_version = "2.7"
22 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 120
3 | exclude = build, dist, env
4 |
5 | [isort]
6 | line_length = 120
7 | not_skip = __init__.py
8 | known_first_party = uberpoet, tests
9 |
10 | [yapf]
11 | based_on_style = google
12 | column_limit = 120
13 |
14 | # Since coverage interferes with debugging, we leave this commented out by default
15 | # [tool:pytest]
16 | # addopts = --cov=uberpoet --cov-report xml:cov.xml --cov-report term-missing
--------------------------------------------------------------------------------
/tests/fixtures/cloc_out.json:
--------------------------------------------------------------------------------
1 | {"header" : {
2 | "cloc_url" : "github.com/AlDanial/cloc",
3 | "cloc_version" : "1.78",
4 | "elapsed_seconds" : 1.01171088218689,
5 | "n_files" : 1,
6 | "n_lines" : 160,
7 | "files_per_second" : 0.988424675079529,
8 | "lines_per_second" : 158.147948012725},
9 | "Swift" :{
10 | "nFiles": 1,
11 | "blank": 28,
12 | "comment": 0,
13 | "code": 132},
14 | "SUM": {
15 | "blank": 28,
16 | "comment": 0,
17 | "code": 132,
18 | "nFiles": 1} }
--------------------------------------------------------------------------------
/tests/fixtures/cpu_log.txt:
--------------------------------------------------------------------------------
1 | 1535510161 CPU usage: 9.12% user, 18.24% sys, 72.62% idle
2 | 1535510162 CPU usage: 5.51% user, 16.78% sys, 77.69% idle
3 | 1535510164 CPU usage: 9.17% user, 18.59% sys, 72.22% idle
4 | 1535510165 CPU usage: 5.64% user, 15.60% sys, 78.75% idle
5 | 1535510166 CPU usage: 4.79% user, 14.97% sys, 80.23% idle
6 | 1535510167 CPU usage: 7.77% user, 20.21% sys, 72.0% idle
7 | 1535510168 CPU usage: 14.54% user, 17.90% sys, 67.54% idle
8 | 1535510169 CPU usage: 4.70% user, 17.94% sys, 77.35% idle
9 | 1535510170 CPU usage: 3.11% user, 12.35% sys, 84.53% idle
10 | 1535510171 CPU usage: 5.99% user, 17.26% sys, 76.73% idle
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
--------------------------------------------------------------------------------
/uberpoet/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name = "uberpoet"
16 |
--------------------------------------------------------------------------------
/genproj.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright (c) 2018 Uber Technologies, Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | from uberpoet.genproj import GenProjCommandLine
18 |
19 | if __name__ == "__main__":
20 | GenProjCommandLine().main()
21 |
--------------------------------------------------------------------------------
/uberpoet/resources/get_market_name.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Copyright (c) 2018 Uber Technologies, Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | defaults read "$HOME/Library/Preferences/com.apple.SystemProfiler.plist" 'CPU Names' | cut -sd '"' -f 4 | uniq
18 |
--------------------------------------------------------------------------------
/multisuite.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright (c) 2018 Uber Technologies, Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | from uberpoet.multisuite import CommandLineMultisuite
18 |
19 | if __name__ == "__main__":
20 | CommandLineMultisuite().main()
21 |
--------------------------------------------------------------------------------
/uberpoet/resources/mockbuckconfig:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # This code was @generated by Uber Poet, a mock application generator.
16 | # Check it out at https://github.com/uber/uber-poet
17 |
18 | [cxx]
19 | default_platform = iphonesimulator-x86_64
20 |
21 | [swift]
22 | version = 5
23 |
24 | [project]
25 | ide = xcode
26 |
--------------------------------------------------------------------------------
/tests/test_dotreader.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | from uberpoet.dotreader import DotFileReader
5 | from uberpoet.moduletree import ModuleNode
6 |
7 |
8 | class TestDotReader(unittest.TestCase):
9 |
10 | def test_read_integration(self):
11 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
12 | main_root_name = "DotReaderMainModule"
13 | dr = DotFileReader()
14 | root, nodes = dr.read_dot_file(test_fixture_path, main_root_name, is_debug=True)
15 |
16 | self.assertEqual(root.name, main_root_name)
17 | self.assertEqual(len(nodes), 338)
18 | self.assertEqual(root.node_type, ModuleNode.APP)
19 | self.assertIn(root, nodes)
20 | self.assertEqual(len(root.deps), 1)
21 | self.assertEqual(len(root.deps[0].deps[0].deps), 5)
22 |
23 | for node in nodes:
24 | if node.name != main_root_name:
25 | self.assertEqual(node.node_type, ModuleNode.LIBRARY)
26 |
--------------------------------------------------------------------------------
/uberpoet/resources/cpu_log.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright (c) 2018 Uber Technologies, Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | date_fn () {
18 | while read -r LINE
19 | do
20 | echo "$(date +%s) $LINE"
21 | done
22 | }
23 |
24 | # This isn't a perfect way to track cpu usage.
25 | # You'll notice some skew in terms of a second or two,
26 | # but it works well enough for our usecase of 700-1500s builds.
27 |
28 | top -l 0 -n 0 | grep --line-buffered CPU | date_fn
--------------------------------------------------------------------------------
/uberpoet/resources/tools/rules/repository_rules.bzl:
--------------------------------------------------------------------------------
1 | """
2 | Repository rules used in the WORKSPACE.
3 | """
4 |
5 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
6 |
7 | def github_repo(name, project, repo, ref, sha256 = None):
8 | github_url = "https://github.com/%s/%s/archive/%s.zip" % (project, repo, ref)
9 | http_archive(
10 | name = name,
11 | strip_prefix = "%s-%s" % (repo, ref.replace("/", "-")),
12 | url = github_url,
13 | sha256 = sha256,
14 | canonical_id = github_url,
15 | )
16 |
17 | def check_execute(repository_ctx, *args, **kwargs):
18 | exec_result = repository_ctx.execute(*args, **kwargs)
19 | if exec_result.return_code != 0:
20 | fail("{}: executing {} {} failed {}:\n{}\n{}".format(
21 | repository_ctx.name,
22 | args,
23 | kwargs,
24 | exec_result.return_code,
25 | exec_result.stdout,
26 | exec_result.stderr,
27 | ).rstrip())
28 | return exec_result
29 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 |
17 |
18 | def do_nothing(_):
19 | pass
20 |
21 |
22 | def integration_test(func):
23 | if 'INTEGRATION' in os.environ:
24 | return func
25 | else:
26 | return do_nothing
27 |
28 |
29 | def read_file(path):
30 | with open(path, 'r') as f:
31 | return f.read()
32 |
33 |
34 | def write_file(path, text):
35 | with open(path, 'w') as f:
36 | f.write(text)
37 |
--------------------------------------------------------------------------------
/tests/test_locreader.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | from uberpoet.filegen import Language
5 | from uberpoet.locreader import LocFileReader
6 |
7 |
8 | class TestLocReader(unittest.TestCase):
9 |
10 | def test_loc_for_module(self):
11 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'loc_mappings.json')
12 | lr = LocFileReader()
13 | lr.read_loc_file(test_fixture_path)
14 |
15 | self.assertEqual(419, lr.loc_for_module("DotReaderLib16"))
16 |
17 | def test_language_for_module(self):
18 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'loc_mappings.json')
19 | lr = LocFileReader()
20 | lr.read_loc_file(test_fixture_path)
21 |
22 | self.assertEqual(Language.OBJC, lr.language_for_module("DotReaderLib0"))
23 | self.assertEqual(Language.SWIFT, lr.language_for_module("DotReaderLib16"))
24 |
25 | def test_throws_exception_without_read(self):
26 | lr = LocFileReader()
27 | self.assertRaises(ValueError, lr.loc_for_module, "DotReaderLib16")
28 |
--------------------------------------------------------------------------------
/tests/test_loccalc.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import unittest
16 |
17 | from uberpoet.filegen import Language
18 | from uberpoet.loccalc import LOCCalculator
19 |
20 |
21 | class TestLOCCalculator(unittest.TestCase):
22 |
23 | def test_calculate_loc(self):
24 | loc_calc = LOCCalculator()
25 | self.assertEqual(loc_calc.calculate_loc('body', Language.SWIFT), 1)
26 |
27 | def test_calculate_loc_multiple_lines(self):
28 | loc_calc = LOCCalculator()
29 | self.assertEqual(loc_calc.calculate_loc('body\ntext', Language.SWIFT), 2)
30 |
--------------------------------------------------------------------------------
/uberpoet/resources/mockpodfile:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # This code was @generated by Uber Poet, a mock application generator.
16 | # Check it out at https://github.com/uber/uber-poet
17 |
18 | platform :ios, '12.0'
19 | use_frameworks!(:linkage => {2})
20 | inhibit_all_warnings!
21 |
22 | install! 'cocoapods',
23 | integrate_targets: false,
24 | deterministic_uuids: {3},
25 | generate_multiple_pod_projects: {4}
26 |
27 | target 'App' do
28 | # app spec integration
29 | {0}
30 |
31 | # local dependencies
32 | {1}
33 | end
34 |
--------------------------------------------------------------------------------
/examples/buckgenproj_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright (c) 2018 Uber Technologies, Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -xe
18 |
19 | GIT_ROOT="$(git rev-parse --show-toplevel)"
20 | GENPROJ_ROOT="$HOME/Desktop/buckgenproj_out"
21 | BUILD_LOG_PATH="$GENPROJ_ROOT/mockapp_build_log.txt"
22 | PROJECT_OUT="$GENPROJ_ROOT/mockapp"
23 |
24 | xcodebuild -version
25 | pipenv install
26 |
27 | pipenv run $GIT_ROOT/genproj.py \
28 | --output_directory "$PROJECT_OUT" \
29 | --blaze_module_path "/mockapp" \
30 | --gen_type flat \
31 | --swift_lines_of_code 150000
32 |
33 | MOCK_WORKSPACE_PATH="$PROJECT_OUT/App/App.xcworkspace"
34 | cd "$PROJECT_OUT"
35 | time buck build "//..." > "$BUILD_LOG_PATH"
36 |
--------------------------------------------------------------------------------
/examples/bazelgenproj_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright (c) 2021 Uber Technologies, Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -xe
18 |
19 | GIT_ROOT="$(git rev-parse --show-toplevel)"
20 | GENPROJ_ROOT="$HOME/Desktop/bazelgenproj_out"
21 | BUILD_LOG_PATH="$GENPROJ_ROOT/mockapp_build_log.txt"
22 | PROJECT_OUT="$GENPROJ_ROOT/mockapp"
23 |
24 | xcodebuild -version
25 | pipenv install
26 |
27 | pipenv run $GIT_ROOT/genproj.py \
28 | --output_directory "$PROJECT_OUT" \
29 | --project_generator_type "bazel" \
30 | --blaze_module_path "/mockapp" \
31 | --gen_type flat \
32 | --swift_lines_of_code 150000
33 |
34 | cd "$PROJECT_OUT"
35 | time bazel build "//..." --incompatible_require_linker_input_cc_api=false > "$BUILD_LOG_PATH"
36 |
--------------------------------------------------------------------------------
/.github/workflows/python-app.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: Build and Run Tests
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build-and-test:
14 |
15 | runs-on: macos-latest
16 | env:
17 | INTEGRATION: 1
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: Set up Python 2.7
21 | uses: actions/setup-python@v2
22 | with:
23 | python-version: 2.7
24 | - name: Install all development dependencies with pipenv
25 | run: |
26 | python -m pip install --upgrade pip
27 | pip install pipenv
28 | pipenv install --dev
29 | - name: Lint with flake8
30 | run: |
31 | pipenv run flake8
32 | - name: Test formatting compliance with yapf
33 | run: |
34 | pipenv run yapf -rd uberpoet/ test/ *py
35 | - name: Test that imports were sorted with isort
36 | run: |
37 | pipenv run isort -c
38 | - name: Run unit and integration tests with pytest
39 | run: |
40 | pipenv run pytest --cov=uberpoet --cov-report xml:cov.xml --cov-report term-missing
41 |
--------------------------------------------------------------------------------
/tests/test_cpulogger.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import unittest
17 |
18 | from uberpoet.cpulogger import CPULog
19 |
20 |
21 | class TestCPULogger(unittest.TestCase):
22 |
23 | def test_cpu_convert(self):
24 | test_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'cpu_log.txt')
25 | with open(test_path, 'r') as log:
26 | out = [CPULog(line) for line in log]
27 | chrome_out = [i.chrome_trace() for i in out]
28 | first_time = 1535510161
29 |
30 | self.assertEqual(len(out), 10)
31 | self.assertEqual(len(chrome_out), 10)
32 | self.assertEqual(chrome_out[0]['ts'], first_time * CPULog.EPOCH_MULT)
33 | self.assertEqual(out[0].epoch, first_time)
34 |
--------------------------------------------------------------------------------
/examples/cpgenproj_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright (c) 2021 Uber Technologies, Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -xe
18 |
19 | GIT_ROOT="$(git rev-parse --show-toplevel)"
20 | GENPROJ_ROOT="$HOME/Desktop/cpgenproj_out"
21 | BUILD_LOG_PATH="$GENPROJ_ROOT/mockapp_build_log.txt"
22 | PROJECT_OUT="$GENPROJ_ROOT/mockapp"
23 |
24 | xcodebuild -version
25 | pipenv install
26 |
27 | pipenv run $GIT_ROOT/genproj.py \
28 | --output_directory "$PROJECT_OUT" \
29 | --project_generator_type "cocoapods" \
30 | --gen_type flat \
31 | --swift_lines_of_code 150000
32 |
33 | MOCK_WORKSPACE_PATH="$PROJECT_OUT/Pods/Pods.xcodeproj"
34 | cd "$PROJECT_OUT"
35 | pod install
36 | time xcodebuild build -scheme AppContainer-App -sdk iphonesimulator -project "$MOCK_WORKSPACE_PATH" > "$BUILD_LOG_PATH"
37 |
--------------------------------------------------------------------------------
/examples/multisuite_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright (c) 2018 Uber Technologies, Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -xe
18 |
19 | GIT_ROOT="$(git rev-parse --show-toplevel)"
20 |
21 | pipenv install
22 |
23 | # You usually want to use `caffeinate` to prevent your computer from going to sleep during
24 | # a multi hour build test suite.
25 | caffeinate -s pipenv run $GIT_ROOT/multisuite.py \
26 | --log_dir "$HOME/Desktop/multisuite_build_results" \
27 | --app_gen_output_dir "$HOME/Desktop/multisuite_build_results/app_gen" \
28 | --test_build_only \
29 | "$@"
30 |
31 |
32 | # Uncomment this if you use --trace_cpu:
33 | # TODO figure out a better way to catch dangling top processes if there is a crash / error in the main program
34 | # echo "Killing potential dangling top subprocesses from CPULog"
35 | # sudo killall top
36 |
--------------------------------------------------------------------------------
/uberpoet/resources/mockbucklibtemplate.bzl:
--------------------------------------------------------------------------------
1 | # BUILD FILE SYNTAX: SKYLARK
2 | # More Info: https://buckbuild.com/concept/skylark.html
3 |
4 | # Copyright (c) 2018 Uber Technologies, Inc.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 |
18 | # This code was @generated by Uber Poet, a mock application generator.
19 | # Check it out at https://github.com/uber/uber-poet
20 |
21 | apple_library(
22 | name = "{0}",
23 | srcs = glob([
24 | "Sources/**/*.m",
25 | "Sources/**/*.swift",
26 | ]),
27 | headers = glob([
28 | "Sources/**/*.h"
29 | ]),
30 | tests = [],
31 | visibility = [
32 | "PUBLIC",
33 | ],
34 | compiler_flags = ["-fmodules"],
35 | configs = {{
36 | "Debug": {{
37 | "SWIFT_WHOLE_MODULE_OPTIMIZATION": "{2}",
38 | }},
39 | }},
40 | deps = [
41 | {1}
42 | ],
43 | )
44 |
--------------------------------------------------------------------------------
/uberpoet/resources/mockbazellibtemplate.bzl:
--------------------------------------------------------------------------------
1 | # BUILD FILE SYNTAX: SKYLARK
2 |
3 | # Copyright (c) 2021 Uber Technologies, Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # This code was @generated by Uber Poet, a mock application generator.
18 | # Check it out at https://github.com/uber/uber-poet
19 |
20 | load("@build_bazel_rules_ios//rules:framework.bzl", "apple_framework")
21 |
22 | apple_framework(
23 | name = "{0}",
24 | srcs = glob([
25 | "Sources/**/*.h",
26 | "Sources/**/*.m",
27 | "Sources/**/*.swift",
28 | ]),
29 | sdk_frameworks = [
30 | "Foundation",
31 | "UIKit",
32 | ],
33 | platforms = {{"ios": "12.0"}},
34 | swift_version = "5.0",
35 | visibility = ["//visibility:public"],
36 | xcconfig = {{
37 | "SWIFT_WHOLE_MODULE_OPTIMIZATION[config=Debug]": "{2}",
38 | }},
39 | deps = [
40 | {1}
41 | ],
42 | )
43 |
--------------------------------------------------------------------------------
/uberpoet/resources/mockbuckapptemplate.bzl:
--------------------------------------------------------------------------------
1 | # BUILD FILE SYNTAX: SKYLARK
2 | # More Info: https://buckbuild.com/concept/skylark.html
3 |
4 | # Copyright (c) 2018 Uber Technologies, Inc.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 |
18 | # This code was @generated by Uber Poet, a mock application generator.
19 | # Check it out at https://github.com/uber/uber-poet
20 |
21 | apple_bundle(
22 | name = "App",
23 | binary = ":AppBinary",
24 | extension = "app",
25 | info_plist = "Info.plist",
26 | )
27 |
28 | apple_binary(
29 | name = "AppBinary",
30 | srcs = glob([
31 | "*.m",
32 | "*.swift",
33 | ]),
34 | headers = glob([
35 | "*.h"
36 | ]),
37 | linker_flags = ["-ObjC"],
38 | compiler_flags = ["-fmodules"],
39 | configs = {{
40 | "Debug": {{
41 | "SWIFT_WHOLE_MODULE_OPTIMIZATION": "{2}",
42 | }},
43 | }},
44 | deps = [
45 | {1}
46 | ],
47 | )
48 |
--------------------------------------------------------------------------------
/uberpoet/resources/mockcplibtemplate.podspec:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # This code was @generated by Uber Poet, a mock application generator.
16 | # Check it out at https://github.com/uber/uber-poet
17 |
18 | Pod::Spec.new do |s|
19 | s.name = '{0}'
20 | s.version = '1.0.0'
21 | s.summary = 'Podspec for {0} library.'
22 | s.authors = 'Generated'
23 | s.homepage = 'https://github.com/uber-research/poet'
24 | s.source = {{ git: 'GENERATED/NOTPUBLISHED', tag: '1.0.0' }}
25 | s.swift_version = '5.0'
26 |
27 | s.pod_target_xcconfig = {{ 'SWIFT_WHOLE_MODULE_OPTIMIZATION[config=Debug]' => '{2}' }}
28 |
29 | s.frameworks = ['Foundation', 'UIKit']
30 |
31 | s.ios.deployment_target = '12.0'
32 |
33 | s.source_files = 'Sources/*.{{h,m,swift}}'
34 |
35 | # dependencies
36 | {1}
37 | end
38 |
--------------------------------------------------------------------------------
/uberpoet/resources/mockcpapptemplate.podspec:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # This code was @generated by Uber Poet, a mock application generator.
16 | # Check it out at https://github.com/uber/uber-poet
17 |
18 | Pod::Spec.new do |s|
19 | s.name = 'AppContainer'
20 | s.version = '1.0.0'
21 | s.summary = 'Podspec for app.'
22 | s.homepage = 'https://github.com/uber-research/poet'
23 | s.authors = 'Generated'
24 | s.source = {{ git: 'GENERATED/NOTPUBLISHED', tag: '1.0.0' }}
25 | s.swift_version = '5.0'
26 |
27 | s.source_files = 'dummy.swift'
28 |
29 | s.ios.deployment_target = '12.0'
30 |
31 | s.app_spec 'App' do |app_spec|
32 | app_spec.pod_target_xcconfig = {{ 'SWIFT_WHOLE_MODULE_OPTIMIZATION[config=Debug]' => '{1}' }}
33 | app_spec.frameworks = ['Foundation', 'UIKit']
34 |
35 | app_spec.source_files = '*.swift'
36 |
37 | # dependencies
38 | {0}
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/uberpoet/memoize.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import collections
16 | import functools
17 |
18 |
19 | # See https://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
20 | class memoized(object):
21 | '''Decorator. Caches a function's return value each time it is called.
22 | If called later with the same arguments, the cached value is returned
23 | (not reevaluated).
24 | '''
25 |
26 | def __init__(self, func):
27 | self.func = func
28 | self.cache = {}
29 |
30 | def __call__(self, *args):
31 | if not isinstance(args, collections.Hashable):
32 | # uncacheable. a list, for instance.
33 | # better to not cache than blow up.
34 | return self.func(*args)
35 | if args in self.cache:
36 | return self.cache[args]
37 | else:
38 | value = self.func(*args)
39 | self.cache[args] = value
40 | return value
41 |
42 | def __repr__(self):
43 | '''Return the function's docstring.'''
44 | return self.func.__doc__
45 |
46 | def __get__(self, obj, objtype):
47 | '''Support instance methods.'''
48 | return functools.partial(self.__call__, obj)
49 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import setuptools
16 |
17 | with open("README.md", "r") as fh:
18 | long_description = fh.read()
19 |
20 | setuptools.setup(
21 | name="uberpoet",
22 | version="1.0.0",
23 | license='Apache License, Version 2.0',
24 | author="Uber Technologies, Inc.",
25 | description="A mock swift project generator & build runner to help benchmark various module configurations.",
26 | long_description=long_description,
27 | long_description_content_type="text/markdown",
28 | url="https://github.com/uber/uber-poet",
29 | packages=setuptools.find_packages(),
30 | classifiers=[
31 | "Programming Language :: Python :: 2.7",
32 | "License :: OSI Approved :: Apache Software License",
33 | "Operating System :: MacOS :: MacOS X",
34 | "Development Status :: 4 - Beta",
35 | "Environment :: Console",
36 | "Environment :: MacOS X",
37 | "Intended Audience :: Developers",
38 | "Topic :: Software Development :: Code Generators",
39 | ],
40 | entry_points={
41 | 'console_scripts': [
42 | 'uberpoet-genproj.py=uberpoet.genproj:main',
43 | 'uberpoet-multisuite.py=uberpoet.multisuite:main',
44 | ],
45 | },
46 | )
47 |
--------------------------------------------------------------------------------
/uberpoet/resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | Uber Mock App
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | com.uber.UberMockApp
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleLocalizations
16 |
17 | en
18 |
19 | CFBundleName
20 | MockApp
21 | CFBundlePackageType
22 | APPL
23 | CFBundleShortVersionString
24 | 4.101.0
25 | CFBundleSignature
26 | ????
27 | CFBundleSupportedPlatforms
28 |
29 | iPhoneOS
30 |
31 | CFBundleURLTypes
32 |
33 |
34 | CFBundleURLName
35 | com.uber.mock.app
36 | CFBundleURLSchemes
37 |
38 | ubermockapp
39 |
40 |
41 |
42 | CFBundleVersion
43 | 4.101.0
44 | LSRequiresIPhoneOS
45 |
46 | MinimumOSVersion
47 | 9.0
48 | UILaunchStoryboardName
49 | MockApp
50 | UIRequiredDeviceCapabilities
51 |
52 | armv7
53 |
54 | UIRequiresFullScreen
55 |
56 | UIStatusBarHidden
57 |
58 | UISupportedInterfaceOrientations
59 |
60 | UIInterfaceOrientationPortrait
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/tests/test_moduletree.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import unittest
16 |
17 | from toposort import toposort_flatten
18 |
19 | from uberpoet.moduletree import ModuleNode
20 |
21 |
22 | class TestModuleTree(unittest.TestCase):
23 |
24 | def test_gen_layered_graph(self):
25 | root, nodes = ModuleNode.gen_layered_graph(10, 10)
26 | self.verify_graph(nodes)
27 | self.assertEqual(len(nodes), 10 * 10 + 1)
28 | self.assertEqual(ModuleNode.APP, root.node_type)
29 |
30 | def test_gen_bs_layered_graph(self):
31 | root, nodes = ModuleNode.gen_layered_big_small_graph(10, 10)
32 | self.verify_graph(nodes)
33 | self.assertEqual(len(nodes), 19 + 1)
34 | self.assertEqual(ModuleNode.APP, root.node_type)
35 |
36 | def verify_graph(self, nodes):
37 | # The generated layered graphs add dependencies randomly to modules within each layer.
38 | # Because of that, we cannot always specify a fixed expected list of nodes without making
39 | # tests indeterministic. Instead, we verify topological sorting by re-creating the same graph,
40 | # sorting it topologicaly and assert the two lists to be the same.
41 | graph = {n: set(n.deps) for n in nodes}
42 | self.assertEqual(toposort_flatten(graph), nodes)
43 |
--------------------------------------------------------------------------------
/uberpoet/resources/mockbazelapptemplate.bzl:
--------------------------------------------------------------------------------
1 | # BUILD FILE SYNTAX: SKYLARK
2 | # More Info: https://buckbuild.com/concept/skylark.html
3 |
4 | # Copyright (c) 2021 Uber Technologies, Inc.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 |
18 | # This code was @generated by Uber Poet, a mock application generator.
19 | # Check it out at https://github.com/uber/uber-poet
20 |
21 | load("@build_bazel_rules_ios//rules:app.bzl", "ios_application")
22 | load("@build_bazel_rules_ios//rules:framework.bzl", "apple_framework")
23 |
24 | apple_framework(
25 | name = "AppContainer",
26 | srcs = glob([
27 | "Sources/**/*.h",
28 | "Sources/**/*.m",
29 | "Sources/**/*.swift",
30 | ]),
31 | platforms = {{"ios": "12.0"}},
32 | swift_version = "5.0",
33 | visibility = ["//visibility:public"],
34 | deps = [
35 | {1}
36 | ],
37 | )
38 |
39 | ios_application(
40 | name = "App",
41 | srcs = glob([
42 | "*.h",
43 | "*.m",
44 | "*.swift",
45 | ]),
46 | bundle_id = "com.uber.UberMockApp",
47 | families = [
48 | "iphone",
49 | "ipad",
50 | ],
51 | infoplists = ["Info.plist"],
52 | minimum_os_version = "12.0",
53 | sdk_frameworks = [
54 | "Foundation",
55 | "UIKit",
56 | ],
57 | linkopts = [
58 | "-ObjC"
59 | ],
60 | swift_version = "5.0",
61 | xcconfig = {{
62 | "SWIFT_WHOLE_MODULE_OPTIMIZATION": "{2}",
63 | }},
64 | deps = [":AppContainer"],
65 | )
66 |
--------------------------------------------------------------------------------
/uberpoet/locreader.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import json
18 | from typing import Dict, List, NoReturn, Set # noqa: F401
19 |
20 | from .filegen import Language
21 |
22 |
23 | class LocFileReader(object):
24 | """
25 | This class reads a JSON file that includes the LOC information for each module and it supplies it to the
26 | project generator.
27 | """
28 |
29 | def __init__(self):
30 | self.cloc_mappings = None
31 |
32 | def read_loc_file(self, loc_file_path):
33 | # type: (str) -> NoReturn
34 | with open(loc_file_path, 'r') as f:
35 | self.cloc_mappings = json.load(f)
36 |
37 | def loc_for_module(self, mod_name):
38 | # type: (str) -> int
39 | if self.cloc_mappings is None:
40 | raise ValueError("Unable to provide LOC for module {}, no data is loaded yet!".format(mod_name))
41 | module_info = self.cloc_mappings[mod_name]
42 | return module_info["loc"] if type(module_info) is dict else module_info
43 |
44 | def language_for_module(self, mod_name):
45 | # type: (str) -> str
46 | if self.cloc_mappings is None:
47 | raise ValueError("Unable to provide LOC for module {}, no data is loaded yet!".format(mod_name))
48 | module_info = self.cloc_mappings[mod_name]
49 | return module_info["language"] if type(module_info) is dict else Language.SWIFT
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # from https://github.com/github/gitignore/blob/master/Python.gitignore
2 | # which was under a CC0 licence / public domain
3 |
4 | # vscode
5 | .vscode
6 |
7 | # Byte-compiled / optimized / DLL files
8 | __pycache__/
9 | *.py[cod]
10 | *$py.class
11 |
12 | # C extensions
13 | *.so
14 |
15 | # Distribution / packaging
16 | .Python
17 | build/
18 | develop-eggs/
19 | dist/
20 | downloads/
21 | eggs/
22 | .eggs/
23 | lib/
24 | lib64/
25 | parts/
26 | sdist/
27 | var/
28 | wheels/
29 | *.egg-info/
30 | .installed.cfg
31 | *.egg
32 | MANIFEST
33 |
34 | # PyInstaller
35 | # Usually these files are written by a python script from a template
36 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
37 | *.manifest
38 | *.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .nox/
48 | .coverage
49 | .coverage.*
50 | .cache
51 | nosetests.xml
52 | coverage.xml
53 | cov.xml
54 |
55 | *.cover
56 | .hypothesis/
57 | .pytest_cache/
58 |
59 | # Translations
60 | *.mo
61 | *.pot
62 |
63 | # Django stuff:
64 | *.log
65 | local_settings.py
66 | db.sqlite3
67 |
68 | # Flask stuff:
69 | instance/
70 | .webassets-cache
71 |
72 | # Scrapy stuff:
73 | .scrapy
74 |
75 | # Sphinx documentation
76 | docs/_build/
77 |
78 | # PyBuilder
79 | target/
80 |
81 | # Jupyter Notebook
82 | .ipynb_checkpoints
83 |
84 | # IPython
85 | profile_default/
86 | ipython_config.py
87 |
88 | # pyenv
89 | .python-version
90 |
91 | # celery beat schedule file
92 | celerybeat-schedule
93 |
94 | # SageMath parsed files
95 | *.sage.py
96 |
97 | # Environments
98 | .env
99 | .venv
100 | env/
101 | venv/
102 | ENV/
103 | env.bak/
104 | venv.bak/
105 |
106 | # Spyder project settings
107 | .spyderproject
108 | .spyproject
109 |
110 | # Rope project settings
111 | .ropeproject
112 |
113 | # mkdocs documentation
114 | /site
115 |
116 | # mypy
117 | .mypy_cache/
118 | .dmypy.json
119 | dmypy.json
120 |
121 | # Pyre type checker
122 | .pyre/
123 |
--------------------------------------------------------------------------------
/docs/layer_types.md:
--------------------------------------------------------------------------------
1 | # Module Generation Graph Types
2 |
3 | ## flat
4 | 
5 |
6 | A module graph that has one app module which depends on one library layer of `module_count` modules. Those modules do not depend on anything.
7 |
8 | ## bs_flat ("Big Small Flat")
9 | 
10 |
11 | A module graph that has one app module which depends on one library layer of modules. Those modules do not depend on anything. Some of the modules in the library layer are 'big', the rest are small. There are `big_module_count` number of big modules, and `small_module_count` small modules.
12 |
13 | ## layered
14 | 
15 |
16 | A module graph of one app module and `app_layer_count` number of library layers. Each layer depends on a random selection of modules in lower layers. There are `module_count` number of library modules.
17 |
18 | Due to probability a random selection of modules won't be built because they won't be connected to main module graph if the module count is large enough, since each module in a layered tree only connects to 5 other modules in a large set.
19 |
20 | ## bs_layered ("Big Small Layered")
21 | 
22 |
23 | A module graph that has one app module, one flat layer of `big_module_count` big modules and `layer_count` layers of `small_module_count` modules under the big module layer which connect to random modules inside like the layed module graph type.
24 |
25 | Due to probability a random selection of modules won't be built because they won't be connected to main module graph if the module count is large enough, since each module in a layered tree only connects to 5 other modules in a large set.
26 |
27 | ## dot
28 | Reads a dot file specified at `dot_file_path` which represents a dependency graph of code modules. Picks `dot_root_node_name` as the app node to generate the app from. You can generate a dot graph of your own buck app by using something like `buck query "deps(//apps/myapp:App)" --dot > file.gv`. Every module in a dot graph mock app is the same size, unlike most applicaitons. Future improvements could co-relate this dot graph with a lines of code file database and directory structures to make proportional modules sizes.
--------------------------------------------------------------------------------
/docs/images/project_gen.xml:
--------------------------------------------------------------------------------
1 | 7Vxdd6I4GP41Xk4PX4Jeau3MXOye7dnuOTNztSeVVOkgYUNsdX79JpIA+UDxA9Q6vWglhIDP+7zfoT33frH6gkE6/xOFMO45VrjquZOe49iea9E/bGSdj/hDPjDDUcgnlQNP0S/IB8W0ZRTCTJpIEIpJlMqDU5QkcEqkMYAxepenvaBYvmsKZlAbeJqCWB/9FoVkno8O+lY5/hVGs7m4s23xMwsgJvOBbA5C9F4Zch967j1GiOSfFqt7GDPwBC75dZ9rzhYPhmFCmlzwNnpNp4vn8K9fyffRP+t/v/49WX0KgnyZNxAv+TfmT0vWAoIZRsuUT4OYwJUJePAsplv6g9nF16U8gWgBCV7TKWKhfn4FZ4hY4L2E23P4lHkFakfIAHARz4qVSxToBw7EHqDYGib3KHmJZj3Hj+nNx8+YfpqxTxpWFKokhGxxi55+n0cEPqVgys6+UwWhY3OyoA8zsYurDZhWsdsitlpE+xqCAqsqgG5b+Dk6gBpQMKRqxg8RJnM0QwmIH8rRsQxlBbZXSMiaWwqwJIgOlSv8gVAqwctudAi49GnREk/hNp7USAHDGJDoTb6tCWV+6SOK6AOV+uBZkkLYjiIoAvAMEn5VKasRxmBdmZayCdn+9ylFn69YEqH4jkfolm5wNp6DDn2BCcSAIHxpWsXxGeiGyTeoVXtmaXAjasXd/IblbarfcdIY6l5ijlBGg5XdboJSk8joZwSjn/AexZT+7iRBCZPVSxTHyhCIo1lCD6cUbEjHx4zoEY1XRvzEIgrDjaBNaiILvzX/48qGxTP4I4NDt1vzR44uKgwBgdmtCWYgh1rD4E4XjWewaaoHOp1oXE00jxi9skhecghXEngVLnWHj/Baw9PT8KQ52ZKidC0QqumAAdFug9n+RXhdiilef2fXU5Xlhz/4cpuDyUo6WkumpEVvLTR4p7vOqXkud+00SHRhEo5YyYBZ8RhkWTSVZVUnA7tlGeyG1jVDu0NnxNix+Uogu/uhooo5hbR0RVunLyu+7TdLe06VmTh6eH1tpjO4MNOph8hXhqjrXhaibr17t0SNyvocXRPCCmdtuxnErWXZ7mX4+/b99rChc3HP6reFKt1k+HXy0qLjmmvt+7pq11Iy/G5dtasn9L9JcTgphicihX9eUuilBOoSs+Xid0WO1dZkGff1uo/fZUnOa6DBx/cdDyvhyEgNTDUyU01H4Hd6F9igndYSVrmYtiaWnULRxO7HcZRmdepW0W+QpfmGgZdoxRTwCOVzaxrcFZgcA0ytVVV1S3jRMK1FUKHpWaeo6bnVRaNm7rUY0qdOQbSbqGhZZ+M+tFpkW0Ukj6mGnsePN0HVXVAcl3EVO1hXDh4hjugXYX64DLy+Vw9+yPHaiUM0Yat3p1+9ZtlXTfLVTWVP4ZZakGtc2DtrXGg3sYZbGXlg2bdksuvbEpMt296bya0zsnEd/5yMNJfj9iVkzY6zrgjZxNE0I+QhdJSouJWGF0O5sxpBR7ZegwM5pzRJ1BJx25zTq5qjZ5rhgk2jfQJTSjiYTBmqfDvWB8uP94uiDDtW3E5DUUMtQ+xYufVSRpFdcllZd7Zf+ekHjia7Tre0eA3M+3lKG2qhz9RrCbYY0tPnqXrL9Sn3GXkXqwnbO2pjeYf5oNY6gYLSVwxdUZqUSXnmFqunV9skYK8GVyVuMbxW0W3rWt8MoAF3W33VgVl83fRVA91+fEhx+DVx37FJgZKJdp1D+rqV+oapnWHEQyyijzCNIBAnyrlLmDv3LSkuYKjbKmOboS1b5TcpGX0A5RDbiXbaqtNo0b4vKHnKLgOx0bC2Aa3ySJ7fzgtNvnMbXBFx0YVyxVVfZrtErgSmTm4eTobRm8QZ/78lezF4zJL5TzwvH7HcME/Ni/NlEJqvk6UgEWPjZRSHPeaZfLBg1j3/TUfGMJnOFwD/7BVvWUA2s3j3Il+MfsnqepXhzeOeIvwVJYcYvpA2fYy6AcIe6iUe29Qpay35DXTDcfFvQppTtobuuj0kdXd9LUg6avJ7biib7H7d8ZpECLL5Bli5nduv9B22t79a92V1r6J01FBQ2k+2mnM3bSk4nlkPO8pBAv8muFKTpnfVgZe5EhxIFXXD7smoQg/Lf2GSTy//EYz78D8=
--------------------------------------------------------------------------------
/uberpoet/loccalc.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import logging
16 | import tempfile
17 | from math import ceil
18 | from os.path import join
19 |
20 | from .commandlineutil import count_loc
21 | from .filegen import Language
22 | from .memoize import memoized
23 |
24 |
25 | class LOCCalculator(object):
26 |
27 | @memoized
28 | def calculate_loc(self, text, language):
29 | # actual code = lines of code, minus whitespace
30 | # calculated using cloc
31 | if language == Language.SWIFT:
32 | extension = '.swift'
33 | elif language == Language.OBJC:
34 | extension = '.m'
35 | else:
36 | raise ValueError("Unknown language: {}".format(language))
37 |
38 | tmp_file_path = join(tempfile.gettempdir(), 'ub_mock_gen_example_file{}'.format(extension))
39 | with open(tmp_file_path, 'w') as f:
40 | f.write(text)
41 | loc = count_loc(tmp_file_path, language)
42 |
43 | if loc == -1:
44 | logging.warning("Using fallback loc calc method due to cloc not being installed.")
45 | if language == Language.SWIFT:
46 | # fallback if cloc is not installed
47 | # this fallback is based on running cloc on the file made by `self.swift_gen.gen_file(3, 3)`
48 | # and saving the result of cloc(file_result.text) / file_result.text_line_count to here:
49 | fallback_code_multiplier = 0.811537333
50 | elif language == Language.OBJC:
51 | # fallback if cloc is not installed
52 | # this fallback is based on running cloc on the file made by `self.objc_source_gen.gen_file(3, 3)`
53 | # and saving the result of cloc(file_result.text) / file_result.text_line_count to here:
54 | fallback_code_multiplier = 0.772727272
55 | else:
56 | raise ValueError('No fallback multiplier calculated for language: {}'.format(language))
57 |
58 | loc = int(ceil(len(text.split('\n')) * fallback_code_multiplier))
59 |
60 | return loc
61 |
--------------------------------------------------------------------------------
/uberpoet/resources/mockappdelegate:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2021 Uber Technologies, Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | {0}
16 |
17 | import UIKit
18 | {1}
19 |
20 | @UIApplicationMain
21 | class AppDelegate: UIResponder, UIApplicationDelegate {{
22 |
23 | var window: UIWindow?
24 |
25 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {{
26 | // Override point for customization after application launch.
27 | {2}
28 | return true
29 | }}
30 |
31 | func applicationWillResignActive(_ application: UIApplication) {{
32 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
33 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
34 | }}
35 |
36 | func applicationDidEnterBackground(_ application: UIApplication) {{
37 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
38 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
39 | }}
40 |
41 | func applicationWillEnterForeground(_ application: UIApplication) {{
42 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
43 | }}
44 |
45 | func applicationDidBecomeActive(_ application: UIApplication) {{
46 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
47 | }}
48 |
49 | func applicationWillTerminate(_ application: UIApplication) {{
50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
51 | }}
52 | }}
53 |
--------------------------------------------------------------------------------
/uberpoet/util.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import distutils.spawn
16 | import math
17 | import os
18 | import subprocess
19 |
20 |
21 | class SeedContainer(object):
22 | """Holds the seed number variable"""
23 | seed = 0
24 |
25 |
26 | def bool_xor(a, b):
27 | """Python's ^ operator is a bitwise xor, so we need to make a boolean equivalent function."""
28 | return (a and not b) or (not a and b)
29 |
30 |
31 | def seed():
32 | """Gives you a unique number for codegen ids"""
33 | SeedContainer.seed += 1
34 | return SeedContainer.seed
35 |
36 |
37 | def first_in_dict(d):
38 | """Grabs the value returned by the first value in d.keys()"""
39 | if len(d) > 0:
40 | k = d.keys()[0]
41 | return d[k]
42 | return None
43 |
44 |
45 | def first_key(dictionary_var):
46 | """dictionary_var.keys()[0]"""
47 | return dictionary_var.keys()[0]
48 |
49 |
50 | def makedir(path):
51 | """Does a mkdir -p `path` if it doesn't exist"""
52 | if not os.path.exists(path):
53 | os.makedirs(path)
54 |
55 |
56 | def merge_lists(two_d_list):
57 | """Merges a 2d array into a 1d array. Ex: [[1,2],[3,4]] becomes [1,2,3,4]"""
58 | # I know this is a fold / reduce, but I got an error when I tried
59 | # the reduce function?
60 | return [i for li in two_d_list for i in li]
61 |
62 |
63 | def percentage_split(list_to_split, percentages):
64 | """Splits an array based on the percentages provided."""
65 | result = []
66 | previous_index = 0
67 | for percentage in percentages:
68 | next_index = int(previous_index + math.ceil((len(list_to_split) * percentage)))
69 | result.append(list_to_split[previous_index:next_index])
70 | previous_index = next_index
71 |
72 | return result
73 |
74 |
75 | def sudo_enabled():
76 | """Tells you if the current 'shell' has sudo permission."""
77 | try:
78 | subprocess.check_call(['sudo', '-n', 'true'])
79 | return True
80 | except subprocess.CalledProcessError:
81 | return False
82 |
83 |
84 | def check_dependent_commands(command_list):
85 | """
86 | Checks if the commands are accessible by the process.
87 | Returns a list of misssing commands. Empty if all commands are available.
88 | """
89 | # noinspection PyUnresolvedReferences
90 | return [command for command in command_list if not distutils.spawn.find_executable(command)]
91 |
92 |
93 | def pad_list(l, size, value=0):
94 | """Pads the right side of the list `l` to `size` if len(l) is less than size with `value`."""
95 | if len(l) >= size:
96 | return l
97 |
98 | return l + ([value] * (size - len(l)))
99 |
100 |
101 | def grab_mac_marketing_name():
102 | """
103 | Returns a string telling you you the macOS marketing name of the device your running on.
104 | Ex: "MacBook Pro (15-inch, 2018)"
105 | """
106 | script_path = os.path.join(os.path.dirname(__file__), "resources", "get_market_name.sh")
107 | return subprocess.check_output([script_path])
108 |
--------------------------------------------------------------------------------
/uberpoet/resources/mockbazelworkspace:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # This code was @generated by Uber Poet, a mock application generator.
16 | # Check it out at https://github.com/uber/uber-poet
17 |
18 | # Description:
19 | # Repository-wide definitions for the seller/register bazel build system.
20 | #
21 | # WORKSPACE defines the root of a bazel environment, and contains infrastructural rules that
22 | # further define the workspace environment. Any "repositories" need to be defined here, so here
23 | # we import all the other bazel projects we consume (mostly skylark libraries or custom bazel
24 | # rules), the core statements that do the fetching of maven artifacts when required, etc.
25 | #
26 | # Rules invoked from the WORKSPACE will only ever be run during the configuration of this bazel
27 | # workspace, and this configuration is cached until invalidated (e.g. bumping a version number,
28 | # or adding a dep, or changing the set of bazel rules we consume). This is the only place where
29 | # bazel permits dynamism based on environment, as it locks down the sandboxes in which each
30 | # build tool executes during the normal build phase.
31 | #
32 | # Specific configuration values have been extracted to make this file a bit more terese, primarily
33 | # versions.bzl which contains globals and environment-wide version numbers shared by everyone,
34 | # and third_party/maven_artifacts.bzl which define any maven artifacts and thier versions that
35 | # will be available to bazel targets (see third_party/MAVEN.md for more details).
36 | #
37 | # Custom rule definitions not imported externally are defined in //third_party/tools/build_defs,
38 | # and other useful bazel tooling will be found in //tools subpackages.
39 |
40 | # Load global values (mostly version numbers) used in the WORKSPACE.
41 | # load(
42 | # "//:workspace_versions.bzl",
43 | # "RULES_APPLE_VERSION",
44 | # )
45 |
46 | workspace(name = "App")
47 |
48 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
49 | load("//tools/rules:repository_rules.bzl", "github_repo")
50 |
51 | github_repo(
52 | name = "build_bazel_rules_ios",
53 | project = "bazel-ios",
54 | ref = "b439882984e3e756f8a54a589b6aa3e7071bb502",
55 | repo = "rules_ios",
56 | sha256 = "5615afd1e20cb33cb26a57c6dc3a0cc586d4571480ec0b24377c9b781337d6de",
57 | )
58 |
59 | load(
60 | "@build_bazel_rules_ios//rules:repositories.bzl",
61 | "rules_ios_dependencies",
62 | )
63 |
64 | rules_ios_dependencies()
65 |
66 | load(
67 | "@bazel_skylib//:workspace.bzl",
68 | "bazel_skylib_workspace",
69 | )
70 |
71 | bazel_skylib_workspace()
72 |
73 | load(
74 | "@build_bazel_rules_apple//apple:repositories.bzl",
75 | "apple_rules_dependencies",
76 | )
77 |
78 | apple_rules_dependencies()
79 |
80 | load(
81 | "@build_bazel_rules_swift//swift:repositories.bzl",
82 | "swift_rules_dependencies",
83 | )
84 |
85 | swift_rules_dependencies()
86 |
87 | load(
88 | "@build_bazel_apple_support//lib:repositories.bzl",
89 | "apple_support_dependencies",
90 | )
91 |
92 | apple_support_dependencies()
93 |
94 | load(
95 | "@com_google_protobuf//:protobuf_deps.bzl",
96 | "protobuf_deps",
97 | )
98 |
99 | protobuf_deps()
100 |
--------------------------------------------------------------------------------
/tests/test_filegen.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import unittest
16 |
17 | from uberpoet.filegen import (FileResult, FuncType, Language, get_func_call_template, get_import_func_calls,
18 | objc_to_objc_func_call_template, objc_to_swift_func_call_template,
19 | swift_to_objc_friendly_func_call_template, swift_to_objc_func_call_template,
20 | swift_to_swift_func_call_template, swift_to_swift_objc_friendly_func_call_template)
21 |
22 |
23 | class TestFileGen(unittest.TestCase):
24 |
25 | def test_func_call_template(self):
26 | # From Swift to ObjC all method calls can be invoked.
27 | self.assertEqual(get_func_call_template(Language.SWIFT, Language.SWIFT, FuncType.SWIFT_ONLY),
28 | swift_to_swift_func_call_template)
29 | self.assertEqual(get_func_call_template(Language.SWIFT, Language.OBJC, FuncType.SWIFT_ONLY),
30 | swift_to_objc_func_call_template)
31 | self.assertEqual(get_func_call_template(Language.SWIFT, Language.SWIFT, FuncType.OBJC_FRIENDLY),
32 | swift_to_swift_objc_friendly_func_call_template)
33 | self.assertEqual(get_func_call_template(Language.SWIFT, Language.OBJC, FuncType.OBJC_FRIENDLY),
34 | swift_to_objc_friendly_func_call_template)
35 |
36 | # From ObjC to Swift we cannot invoke methods that are only available to Swift.
37 | with self.assertRaises(ValueError):
38 | get_func_call_template(Language.OBJC, Language.SWIFT, FuncType.SWIFT_ONLY)
39 | get_func_call_template(Language.OBJC, Language.OBJC, FuncType.SWIFT_ONLY)
40 |
41 | # From ObjC to Swift with ObjC friendly methods
42 | self.assertEqual(get_func_call_template(Language.OBJC, Language.SWIFT, FuncType.OBJC_FRIENDLY),
43 | objc_to_swift_func_call_template)
44 | self.assertEqual(get_func_call_template(Language.OBJC, Language.OBJC, FuncType.OBJC_FRIENDLY),
45 | objc_to_objc_func_call_template)
46 |
47 | def test_import_func_calls_swift(self):
48 | expected_swift_func_calls = """MyClass0().complexCrap0(arg: 4, stuff: 2)
49 | MyClass0().complexCrap1(arg: 4, stuff: 2)
50 | MyClass0().complexCrap2(arg: 4, stuff: 2)
51 | MyClass0().complexStuff0(arg: "4")"""
52 | swift_import = self._create_import('MockLib0', Language.SWIFT)
53 | self.assertEqual(get_import_func_calls(Language.SWIFT, [swift_import]), expected_swift_func_calls)
54 |
55 | def test_import_func_calls_objc(self):
56 | expected_objc_func_calls = """[[[MyClass_0 alloc] init] complexCrap0:4 stuff:@"2"];
57 | [[[MyClass_0 alloc] init] complexCrap1:4 stuff:@"2"];
58 | [[[MyClass_0 alloc] init] complexCrap2:4 stuff:@"2"];"""
59 | objc_import = self._create_import('MockLib0', Language.OBJC)
60 | self.assertEqual(get_import_func_calls(Language.OBJC, [objc_import]), expected_objc_func_calls)
61 |
62 | def test_import_func_calls_for_str(self):
63 | self.assertEqual(get_import_func_calls(Language.SWIFT, ['File.h']), '')
64 |
65 | def _create_import(self, name, language):
66 | file_name = 'File0.swift' if language == Language.SWIFT else 'File0.m'
67 | if language == Language.SWIFT:
68 | class_functions = {FuncType.SWIFT_ONLY: [0, 1, 2], FuncType.OBJC_FRIENDLY: [0]}
69 | elif language == Language.OBJC:
70 | class_functions = {FuncType.OBJC_FRIENDLY: [0, 1, 2]}
71 | file_result = FileResult('body', [], {0: class_functions})
72 | return {name: {'files': {file_name: file_result}, 'loc': 150, 'language': language}}
73 |
--------------------------------------------------------------------------------
/uberpoet/moduletree.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import random
18 |
19 | from toposort import toposort_flatten
20 |
21 | from .util import merge_lists
22 |
23 |
24 | class ModuleGenType(object):
25 | flat = 'flat'
26 | bs_flat = 'bs_flat'
27 | layered = 'layered'
28 | bs_layered = 'bs_layered'
29 | dot = 'dot'
30 |
31 | @staticmethod
32 | def enum_list():
33 | return [
34 | ModuleGenType.flat,
35 | ModuleGenType.bs_flat,
36 | ModuleGenType.layered,
37 | ModuleGenType.bs_layered,
38 | ModuleGenType.dot,
39 | ]
40 |
41 |
42 | class ModuleNode(object):
43 | """Represents a module in a dependency graph"""
44 |
45 | APP = 'APP'
46 | LIBRARY = 'LIBRARY'
47 |
48 | def __init__(self, name, node_type, deps=None):
49 | super(ModuleNode, self).__init__()
50 | if deps is None:
51 | deps = []
52 | self.name = name
53 | self.node_type = node_type # app or library
54 | self.deps = deps
55 | # How many code units the module represents. Bigger modules would
56 | # have more code units than smaller modules, with 1 being the 'standard' size.
57 | self.code_units = 1
58 | self.extra_info = None # useful for file indexes and such
59 |
60 | def __hash__(self):
61 | return hash((self.name, self.node_type))
62 |
63 | def __eq__(self, other):
64 | return (self.name, self.node_type) == (other.name, other.node_type)
65 |
66 | def __repr__(self):
67 | return "ModuleNode('{}','{}')".format(self.name, self.node_type)
68 |
69 | def __str__(self):
70 | extra = True if self.extra_info else False
71 | return "<{} : {} deps: {} has_info: {}>".format(self.name, self.node_type, len(self.deps), extra)
72 |
73 | @staticmethod
74 | def gen_layered_graph(layer_count, nodes_per_layer, deps_per_node=5):
75 | """Generates a module dependency graph that has `layer_count` layers,
76 | with each module only depending on a random selection of the modules
77 | below it"""
78 |
79 | def node(f_layer, f_node):
80 | return ModuleNode('MockLib{}_{}'.format(f_layer, f_node), ModuleNode.LIBRARY)
81 |
82 | layers = [[node(l, n) for n in xrange(nodes_per_layer)] for l in xrange(layer_count)]
83 | app_node = ModuleNode('App', ModuleNode.APP, layers[0])
84 |
85 | node_graph = {}
86 |
87 | for i, layer in enumerate(layers):
88 | lower_layers = layers[(i + 1):] if i != len(layers) - 1 else []
89 | lower_merged = merge_lists(lower_layers)
90 | for node in layer:
91 | if deps_per_node < len(lower_merged):
92 | node.deps = random.sample(lower_merged, deps_per_node)
93 | else:
94 | node.deps = lower_merged
95 |
96 | node_graph[node] = set(node.deps)
97 |
98 | return app_node, (toposort_flatten(node_graph) + [app_node])
99 |
100 | @staticmethod
101 | def gen_flat_graph(module_count):
102 | """Generates a module dependency graph that depends on `module_count`
103 | libraries that don't depend on anything"""
104 | libraries = [ModuleNode('MockLib{}'.format(i), ModuleNode.LIBRARY) for i in xrange(module_count)]
105 | app_node = ModuleNode('App', ModuleNode.APP, libraries)
106 |
107 | return app_node, (libraries + [app_node])
108 |
109 | @staticmethod
110 | def gen_flat_big_small_graph(big_mod_count, small_mod_count):
111 | big_libs = [ModuleNode('BigMockLib{}'.format(i), ModuleNode.LIBRARY) for i in xrange(big_mod_count)]
112 | small_libs = [ModuleNode('SmallMockLib{}'.format(i), ModuleNode.LIBRARY) for i in xrange(small_mod_count)]
113 | app_node = ModuleNode('App', ModuleNode.APP, big_libs + small_libs)
114 |
115 | for l in big_libs:
116 | l.code_units = 20
117 |
118 | return app_node, (big_libs + small_libs + [app_node])
119 |
120 | @staticmethod
121 | def gen_layered_big_small_graph(big_mod_count, small_mod_count):
122 | big_libs = [ModuleNode('AppMockLib{}'.format(i), ModuleNode.LIBRARY) for i in xrange(big_mod_count)]
123 | app_node = ModuleNode('App', ModuleNode.APP, big_libs)
124 |
125 | layer_count = 3
126 | layer_mod_count = small_mod_count / layer_count
127 | deps_per_layer = layer_count / 2 if layer_count >= 2 else 1
128 |
129 | layer_app_node, layer_nodes = ModuleNode.gen_layered_graph(layer_count, layer_mod_count, deps_per_layer)
130 | layer_nodes = [layer_item for layer_item in layer_nodes if layer_item != layer_app_node]
131 |
132 | for l in big_libs:
133 | l.code_units = 20
134 | l.deps = layer_app_node.deps
135 |
136 | node_graph = {n: set(n.deps) for n in big_libs + layer_nodes}
137 | return app_node, (toposort_flatten(node_graph) + [app_node])
138 |
--------------------------------------------------------------------------------
/uberpoet/cpulogger.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import logging
16 | import os
17 | import subprocess
18 | import sys
19 | from tempfile import TemporaryFile
20 |
21 |
22 | class CPULog(object):
23 | """An object representation of a CPU state log"""
24 | EPOCH_MULT = 1000000
25 |
26 | def __init__(self, line=None):
27 | self.epoch = None
28 | self.sys = None
29 | self.user = None
30 | self.idle = None
31 | if line:
32 | self.parse_line(line)
33 |
34 | def parse_line(self, line):
35 | """Parses a top CPU log string into data for this object"""
36 |
37 | def percent_to_num(raw):
38 | return float(raw[:-1]) * 0.01
39 |
40 | line = str(line)
41 | items = line.split(" ")
42 | # filter out null chars that sometimes showup
43 | epoch_str = items[0].translate(None, '\x00')
44 |
45 | self.epoch = int(epoch_str)
46 | self.user = percent_to_num(items[3])
47 | self.sys = percent_to_num(items[5])
48 | self.idle = percent_to_num(items[7])
49 |
50 | def chrome_trace(self):
51 | """Returns the chrome trace json representation of the the object"""
52 | return {
53 | "name": "cpu",
54 | "ph": "C",
55 | "pid": 1,
56 | "ts": self.chrome_epoch,
57 | "args": {
58 | "user": self.user,
59 | "sys": self.sys,
60 | "idle": self.idle
61 | }
62 | }
63 |
64 | @property
65 | def chrome_epoch(self):
66 | """Turns the internal epoch represenation into the time unit that chrome traces expect"""
67 | return self.epoch * CPULog.EPOCH_MULT
68 |
69 | def chrome_epoch_in_range(self, min_ts, max_ts):
70 | """Returns true if this object is within the specified time range"""
71 | return min_ts <= self.chrome_epoch <= max_ts
72 |
73 | @staticmethod
74 | def find_timestamp_range(traces):
75 | """
76 | Finds the minimum and maximum times of items inside a chrome trace list,
77 | so the CPU log won't add CPU items outside of it's range.
78 | """
79 | min_ts = sys.maxsize
80 | max_ts = -1
81 | for trace in traces:
82 | ts = trace['ts']
83 | if ts < min_ts:
84 | min_ts = ts
85 | elif ts > max_ts:
86 | max_ts = ts
87 | return min_ts, max_ts
88 |
89 | @staticmethod
90 | def apply_log_to_trace(log_list, traces):
91 | """Adds CPU items to a chrome trace"""
92 | min_ts, max_ts = CPULog.find_timestamp_range(traces)
93 | traces_in_range = [i.chrome_trace() for i in log_list if i.chrome_epoch_in_range(min_ts, max_ts)]
94 | return traces + traces_in_range
95 |
96 |
97 | # TODO Use psutil instead of top bash script to fix app killing issues?
98 | class CPULogger(object):
99 | """
100 | This class uses the top command under the hood to continiously log system cpu
101 | utilization on the current system.
102 | """
103 |
104 | def __init__(self):
105 | self.process = None
106 | self.output = TemporaryFile()
107 |
108 | def __del__(self):
109 | self.output.close()
110 |
111 | def start(self):
112 | """Starts the CPU logger"""
113 | self.stop()
114 | logging.warning('You will probably have to call `sudo killall top` to'
115 | ' kill the CPU monitor after this python script finishes execution.')
116 | script_path = os.path.join(os.path.dirname(__file__), "resources", "cpu_log.sh")
117 | self.process = subprocess.Popen([script_path], stdout=self.output)
118 |
119 | def stop(self):
120 | """
121 | Should stop the CPU logger.
122 |
123 | MAJOR
124 | TODO doesn't do anything, either because top runs as root and thus you need
125 | to be root to kill it or some process group thing we are not doing properly
126 | self.process.kill() doesnt work either.
127 | """
128 | if self.process:
129 | self.process.terminate()
130 | self.process = None
131 |
132 | def kill(self):
133 | """
134 | Attempts to use sudo to kill the dangling CPU monitor. Currently doesn't seem to work,
135 | you need to kill the top process outside of the python process.
136 | """
137 | self.stop()
138 | command = ['sudo', 'killall', 'top']
139 | logging.warning('Killing dangling CPU monitor with sudo. Command: `%s`', ' '.join(command))
140 | try:
141 | subprocess.check_call(command)
142 | except subprocess.CalledProcessError:
143 | logging.info("Error killing top command")
144 |
145 | def process_log(self):
146 | """Stops the CPU logger and converts the internal CPU log strings into a list of CPULog objects."""
147 | self.stop()
148 | self.output.seek(0)
149 | out = [CPULog(line) for line in self.output]
150 | self.output.close()
151 | self.output = TemporaryFile()
152 | return out
153 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | Uber welcomes contributions of all kinds and sizes. This includes everything from from simple bug reports to large features.
4 |
5 | Before we can accept your contributions, we kindly ask you to sign our [Contributor License Agreement](https://cla-assistant.io/uber/uber-poet).
6 |
7 | ## Workflow
8 |
9 | We love GitHub issues!
10 |
11 | For small feature requests, an issue first proposing it for discussion or demo implementation in a PR suffice.
12 |
13 | For big features, please open an issue so that we can agree on the direction, and hopefully avoid investing a lot of time on a feature that might need reworking.
14 |
15 | Small pull requests for things like typos, bug fixes, etc are always welcome.
16 |
17 | ## DOs and DON'Ts
18 |
19 | * DO read this document to understand how Uber Poet is built and how we develop.
20 | * DO write your change to conform to our formatting, lint, import sorting, etc tools before uploading your pull request.
21 | * DO add types if possible with new and old code. Types help with refactoring and are unit tests you don't have to write.
22 | * DO include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken.
23 | * DO keep the discussions focused. When a new or related topic comes up it's often better to create new issue than to side track the discussion.
24 | * DON'T not copy code from websites with licenses that are incompatible with Apache 2.0. This means you cannot copy from StackOverflow, because it's Creative Commons BY-SA license which is not compatible.
25 | * DON'T submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it.
26 |
27 | ## Basic Getting Started
28 |
29 | * On a macOS 10.13+ machine, install Xcode : `xcode-select --install`
30 | * Install [pipenv](https://pipenv.readthedocs.io/en/latest/)
31 | * Clone the repo: `git clone https://github.com/uber/uber-patcher.git`
32 | * Install python dependencies: `pipenv install --dev`
33 | * Test if your pipenv setup is working by running unit tests: `pipenv run pytest`
34 | * You can also run longer running integration tests with `INTEGRATION=1 pipenv run pytest`
35 | * Edit code with your favorite text editor or IDE!
36 |
37 | ## How we Develop
38 |
39 | * We used [Visual Studio Code](https://code.visualstudio.com) at first, but then moved to [pycharm](https://www.jetbrains.com/pycharm/) because of it's code inspector, richer refactoring tools and less configuration required to do basic things like testing. You can use any code editor you would like, but please use PyCharm's [Inspect Code](https://www.jetbrains.com/help/pycharm/running-inspections.html) before you push.
40 | * It's a goal to eventually move this code base to python 3, probably when python 2.7 won't be maintained anymore after 2020.
41 |
42 | Tools used for managing the code base:
43 | * pytest for running tests, basic unittest for writing tests. Run tests with your IDE or `pipenv run pytest`.
44 | * yapf for formatting
45 | * isort for sorting imports
46 | * flake8 & pylint for linting
47 | * PyCharm's "[Inspect Code](https://www.jetbrains.com/help/pycharm/running-inspections.html)" tool (not enforced by ci since it's difficult to use via command line)
48 | * pipenv to manage application dependencies
49 | * Tool configuration is kept in the `setup.cfg` file. Please use that as you develop.
50 |
51 | I would suggest running the dev tool versions that come with the repo, to avoid issues with github actions CI not passing. Look at the [github actions yaml](../.github/workflows/python-app.yml) to see what commands it executes. The versions they execute although are not ones that would actually update your code to automatically fix them, so a general pre-commit workflow would be:
52 |
53 | ```bash
54 | pipenv install --dev #only need to run this once
55 | INTEGRATION=1 pipenv run pytest --cov=uberpoet --cov-report xml:cov.xml --cov-report term-missing #fix failing tests
56 | pipenv run yapf -ri uberpoet/ test/ *py # -r = recursive , -i = in-place, yapf is an autoformatter
57 | pipenv run isort # isort fixes in place by default, isort = import sort
58 | pipenv run flake8 # manually fix any issues the flake8 linter brings up
59 | ```
60 |
61 | Make sure to check the [.travis.yml](../.travis.yml) file to see what is actually run in case this file is out of date.
62 |
63 | ## Basic App Architecture
64 |
65 | If you were to summarize Uber Poet as some python pseudocode:
66 |
67 | ```python
68 | def make_project(config):
69 | abstract_dependency_graph = config.graph_generation_function(config.project_generation_options)
70 | swift_file_maker = SwiftFileGenerator()
71 | project_gen = BuckProjectGenerator(swift_file_maker)
72 | project_gen.generate_project_from(abstract_dependency_graph)
73 | project_gen.write_to_folder(config.output_path)
74 |
75 | def multisuite(config):
76 | original_state = save_xcode_state()
77 |
78 | for xcode_config in config.xcode_configs:
79 | set_xcode_state(xcode_config)
80 |
81 | for gen_func in config.graph_generation_functions:
82 | proj_config = ProjectConfig(gen_func, join(config.output_path, gen_func.name)
83 | make_project(proj_config)
84 |
85 | trace = TimeAndCPUTracer().start()
86 | build_project(proj_config)
87 | trace.stop()
88 |
89 | trace.append_result_to_csv(config, proj_config)
90 |
91 | set_xcode_state(original_state)
92 | ```
93 |
94 | 
95 |
96 | Generating a mock app consists of 3 parts:
97 |
98 | * Generating an abstract module dependency graph that represents the mock app. (`ModuleNode` in `moduletree.py`)
99 | * Feeding this graph into a build description generator (ex: `BuckProjectGenerator` in `projectgen.py`), which creates project config files.
100 | * And the build description generator using a file generator (ex `SwiftFileGenerator` in `filegen.py`) creating mock code files.
101 | * And then writing all these files into a tree of folders
102 |
103 | Most variation & configuration resides in the graph objects that the graph generation functions create. So the if command line says there are 50 modules or 100 modules with a [`bs_layered`](docs/layer_types.md) graph type, that will show up in the generated abstract graph.
104 |
105 | `GenProjCommandLine` from `genproj.py` is the UI that the user's configuration for generating a mock app is passed into the above process.
106 |
107 | `CommandLineMultisuite` from `multisuite.py`, does the same thing as `genproj.py`, but with a list of project generators, and builds these projects and records the build times. It also gives you configuration options in how building is done. Multisuite also uses code in `statementmanagement.py` & `cpulogger.py` to help it manage Xcode build configuration and track CPU usage.
108 |
109 | `dotreader.py` is used to generate dependency graphs from dot files.
110 |
--------------------------------------------------------------------------------
/uberpoet/statemanagement.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import getpass
18 | import logging
19 | import os
20 | import shutil
21 | import subprocess
22 | import sys
23 | from os.path import join
24 |
25 | from .util import pad_list
26 |
27 |
28 | class SettingsState(object):
29 |
30 | def __init__(self, git_root):
31 | self.git_root = git_root
32 | self.have_backed_up = False
33 | self.local_path = join(self.git_root, '.buckconfig.local')
34 | self.backup_path = join(self.git_root, '.buckconfig.local.bak')
35 | self.select_path = None
36 |
37 | def save_buckconfig_local(self):
38 | if os.path.exists(self.backup_path):
39 | os.remove(self.backup_path)
40 | if os.path.exists(self.local_path):
41 | logging.info('Backing up .buckconfig.local to .buckconfig.local.bak')
42 | shutil.copy2(self.local_path, self.backup_path)
43 | self.have_backed_up = True
44 | else:
45 | logging.info('No .buckconfig.local to back up, skipping')
46 |
47 | def restore_buckconfig_local(self):
48 | if self.have_backed_up:
49 | logging.info('Restoring .buckconfig.local')
50 | os.remove(self.local_path)
51 | shutil.copy2(self.backup_path, self.local_path)
52 | os.remove(self.backup_path)
53 | self.have_backed_up = False
54 |
55 | def save_xcode_select(self):
56 | self.select_path = subprocess.check_output(['xcode-select', '-p']).rstrip()
57 |
58 | def restore_xcode_select(self):
59 | if self.select_path:
60 | logging.info('Restoring xcode-select path to %s', self.select_path)
61 | subprocess.check_call(['sudo', 'xcode-select', '-s', self.select_path])
62 |
63 |
64 | class XcodeManager(object):
65 |
66 | @staticmethod
67 | def get_xcode_dirs(containing_dir='/Applications'):
68 | items = os.listdir(containing_dir)
69 | return [join(containing_dir, d) for d in items if 'xcode' in d.lower() and d.endswith('app')]
70 |
71 | @staticmethod
72 | def get_current_xcode_version():
73 | version_out = subprocess.check_output(['xcodebuild', '-version']).splitlines()
74 | version_num = version_out[0].split(' ')[1]
75 | build_id = version_out[1].split(' ')[2]
76 | return version_num, build_id
77 |
78 | @staticmethod
79 | def switch_xcode_version(xcode_path):
80 | subprocess.check_call(['sudo', 'xcode-select', '-s', xcode_path])
81 |
82 | def xcode_version_of_path(self, path):
83 | try:
84 | self.switch_xcode_version(path)
85 | except subprocess.CalledProcessError:
86 | return None, None
87 | return self.get_current_xcode_version()
88 |
89 | def discover_xcode_versions(self):
90 | settings = SettingsState('/')
91 | settings.save_xcode_select()
92 |
93 | candidates = self.get_xcode_dirs()
94 | out = {}
95 | for path in candidates:
96 | version, build = self.xcode_version_of_path(path)
97 | if version:
98 | out[(version, build)] = path
99 |
100 | settings.restore_xcode_select()
101 |
102 | out = XcodeVersion.choose_latest_major_versions(out)
103 |
104 | return out
105 |
106 | @staticmethod
107 | def _get_global_module_cache_dir():
108 | try:
109 | username = getpass.getuser()
110 | except Exception as e:
111 | sys.exit(str(e))
112 |
113 | cache_dir = subprocess.check_output(['getconf', 'DARWIN_USER_CACHE_DIR']).rstrip()
114 | user_dir = 'org.llvm.clang.{}'.format(username)
115 | return os.path.join(cache_dir, user_dir, 'ModuleCache')
116 |
117 | def clean_caches(self):
118 | logging.info('Cleaning Xcode caches...')
119 |
120 | directories_to_delete = (
121 | '~/Library/Caches/com.apple.dt.Xcode',
122 | '~/Library/Developer/Xcode/DerivedData',
123 | self._get_global_module_cache_dir(),
124 | )
125 |
126 | for directory in directories_to_delete:
127 | full_path = os.path.expanduser(directory)
128 | logging.info('Removing %s', full_path)
129 | subprocess.check_call(['rm', '-fr', full_path])
130 |
131 |
132 | class XcodeVersion(object):
133 | """Represents an xcode version that is comparable"""
134 |
135 | def __init__(self, raw_version, build):
136 | self.version = self.numeric_version(raw_version)
137 | self.raw_version = raw_version
138 | self.build = build
139 |
140 | @staticmethod
141 | def numeric_version(raw):
142 | return pad_list([int(x) for x in raw.split('.')], 3, 0)
143 |
144 | @property
145 | def major(self):
146 | return self.version[0]
147 |
148 | @property
149 | def raw(self):
150 | return self.raw_version, self.build
151 |
152 | @staticmethod
153 | def choose_latest_major_versions(raw_versions):
154 | """
155 | This selects the latest version for each major version of xcode in a set of xcode paths.
156 | raw_versions is a {(version_str, build_str): xcode_path_str} dictionary.
157 | """
158 |
159 | versions = {XcodeVersion(raw_version, build): path for (raw_version, build), path in raw_versions.iteritems()}
160 |
161 | major_seperated = {}
162 | for version, path in versions.iteritems():
163 | subset = major_seperated.get(version.major, {})
164 | subset[version] = path
165 | major_seperated[version.major] = subset
166 |
167 | out = {}
168 | for subset in major_seperated.values():
169 | max_version = max(subset.keys())
170 | out[max_version.raw] = subset[max_version]
171 |
172 | return out
173 |
174 | def __repr__(self):
175 | return "XcodeVersion('{}','{}')".format(self.raw_version, self.build)
176 |
177 | def __eq__(self, b):
178 | if not (tuple(self.version) == tuple(b.version)):
179 | return False
180 | if self.build == b.build:
181 | return True
182 | return False
183 |
184 | def __gt__(self, b):
185 | if self.version == b.version:
186 | if self.build > b.build:
187 | return True
188 | return False
189 |
190 | if tuple(self.version) > tuple(b.version):
191 | return True
192 |
193 | return False
194 |
--------------------------------------------------------------------------------
/uberpoet/genproj.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import argparse
18 | import json
19 | import logging
20 | import sys
21 | import time
22 | from os.path import join
23 |
24 | from . import blazeprojectgen, commandlineutil, cpprojectgen
25 | from .moduletree import ModuleGenType
26 |
27 |
28 | class GenProjCommandLine(object):
29 |
30 | @staticmethod
31 | def make_args(args):
32 | """Parses command line arguments"""
33 | arg_desc = 'Generate a fake test project with many modules'
34 |
35 | parser = argparse.ArgumentParser(description=arg_desc)
36 |
37 | parser.add_argument('-o', '--output_directory', required=True, help='Where the mock project should be output.')
38 | parser.add_argument(
39 | '-pgt',
40 | '--project_generator_type',
41 | choices=['buck', 'bazel', 'cocoapods'],
42 | default='buck',
43 | required=False,
44 | help='The project generator type to use. Supported types are Buck, Bazel and CocoaPods. Default is `buck`')
45 | parser.add_argument(
46 | '-bmp',
47 | '--blaze_module_path',
48 | help='The root of the Buck or Bazel dependency path of the generated code. Only used if Buck or Bazel '
49 | 'generator type is used.')
50 | parser.add_argument(
51 | '-gt',
52 | '--gen_type',
53 | required=True,
54 | choices=ModuleGenType.enum_list(),
55 | help='What kind of mock app generation you want. See layer_types.md for a description of graph types.')
56 | parser.add_argument(
57 | '-wmo',
58 | '--use_wmo',
59 | default=False,
60 | help='Whether or not to use whole module optimization when building swift modules.')
61 | parser.add_argument(
62 | '-udl',
63 | '--use_dynamic_linking',
64 | default=False,
65 | help='Whether or not to generate a project in which the modules are dynamically linked. By default all '
66 | 'projects use static linking. This option is currently used only by the CocoaPods generator.')
67 | parser.add_argument(
68 | '--print_dependency_graph',
69 | default=False,
70 | help='If true, prints out the dependency edge list and exits instead of generating an application.')
71 | # CocoaPods specific options
72 | parser.add_argument(
73 | '--cocoapods_use_deterministic_uuids',
74 | default=True,
75 | help='Whether to use deterministic uuids within the CocoaPods generated project. Defaults to `true`.')
76 | parser.add_argument(
77 | '--cocoapods_generate_multiple_pod_projects',
78 | default=False,
79 | help='Whether to generate multiple pods projects. Defaults to `false`.')
80 |
81 | commandlineutil.AppGenerationConfig.add_app_gen_options(parser)
82 | args = parser.parse_args(args)
83 | commandlineutil.AppGenerationConfig.validate_app_gen_options(args)
84 |
85 | return args
86 |
87 | def main(self, args=None):
88 | if args is None:
89 | args = sys.argv[1:]
90 |
91 | logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(funcName)s: %(message)s')
92 | start = time.time()
93 |
94 | args = self.make_args(args)
95 |
96 | graph_config = commandlineutil.AppGenerationConfig()
97 | graph_config.pull_from_args(args)
98 | app_node, node_list = commandlineutil.gen_graph(args.gen_type, graph_config)
99 |
100 | if args.print_dependency_graph:
101 | print_nodes(node_list)
102 | exit(0)
103 |
104 | commandlineutil.del_old_output_dir(args.output_directory)
105 | gen = project_generator_for_arg(args)
106 |
107 | logging.info("Project Generator type: %s", args.project_generator_type)
108 | logging.info("Generation type: %s", args.gen_type)
109 | logging.info("Creating a {} module count mock app in {}".format(len(node_list), args.output_directory))
110 | logging.info("Example command to generate Xcode workspace: $ {}".format(gen.example_command()))
111 |
112 | gen.gen_app(app_node, node_list, graph_config.swift_lines_of_code, graph_config.objc_lines_of_code,
113 | graph_config.loc_json_file_path)
114 |
115 | fin = time.time()
116 | logging.info("Done in %f s", fin - start)
117 |
118 | project_info = {
119 | "generator_type": args.project_generator_type,
120 | "graph_config": args.gen_type,
121 | "options": {
122 | "use_wmo": bool(args.use_wmo),
123 | "use_dynamic_linking": bool(args.use_dynamic_linking),
124 | "swift_lines_of_code": args.swift_lines_of_code,
125 | "objc_lines_of_code": args.objc_lines_of_code
126 | },
127 | "time_to_generate": fin - start
128 | }
129 | with open(join(args.output_directory, "project_info.json"), "w") as project_info_json_file:
130 | json.dump(project_info, project_info_json_file)
131 |
132 |
133 | def print_nodes(node_list):
134 | edges = [(node.name, dep.name) for node in node_list for dep in node.deps]
135 | for edge in edges:
136 | print(edge[0], edge[1])
137 |
138 |
139 | def project_generator_for_arg(args):
140 | if args.project_generator_type == 'buck' or args.project_generator_type == 'bazel':
141 | if not args.blaze_module_path:
142 | raise ValueError("Must supply --blaze_module_path when using the Buck or Bazel generators.")
143 | return blazeprojectgen.BlazeProjectGenerator(
144 | args.output_directory, args.blaze_module_path, use_wmo=args.use_wmo, flavor=args.project_generator_type)
145 | elif args.project_generator_type == 'cocoapods':
146 | return cpprojectgen.CocoaPodsProjectGenerator(
147 | args.output_directory,
148 | use_wmo=args.use_wmo,
149 | use_dynamic_linking=args.use_dynamic_linking,
150 | use_deterministic_uuids=args.cocoapods_use_deterministic_uuids,
151 | generate_multiple_pod_projects=args.cocoapods_generate_multiple_pod_projects)
152 | else:
153 | raise ValueError("Unknown project generator arg: " + str(args.project_generator_type))
154 |
155 |
156 | def main():
157 | GenProjCommandLine().main()
158 |
159 |
160 | if __name__ == '__main__':
161 | main()
162 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Uber Poet
2 |
3 | [](https://bestpractices.coreinfrastructure.org/projects/2983)
4 | [](https://github.com/uber/uber-poet/actions)
5 |
6 | This python app makes mock Xcode Swift / ObjC app projects with [Buck](https://buckbuild.com/), [Bazel](https://bazel.build/)
7 | and [CocoaPods](https://cocoapods.org). It lets us test different Swift / ObjC module configurations to see how much build speed is affected by different [dependency graphs](docs/layer_types.md) with identical amounts of code. There are two main command line apps:
8 |
9 | * `genproj.py` which generates one app which you have to build manually yourself. Either with `buck`, `bazel` or `xcodebuild`.
10 | * `multisuite.py`, which generates all module configs, builds them, records how long they take to build into a CSV and outputs it's results to a directory passed in the command line. Essentially a benchmark test suite. Can take several hours to run depending how many lines of code each app takes.
11 |
12 | This app was architected so other languages, graph generators or build systems wouldn't be much work to add. Theoretically you could extend this app to generate java gradle android apps with the same [dependency graph types](docs/layer_types.md).
13 |
14 | ## How to Install / Dependencies
15 |
16 | With a mac computer that can run macOS 10.13+, install all the dependencies below:
17 |
18 | * macOS 10.13.X+, untested on older versions.
19 | * Python 2.7.X (pre-installed on macOS 10.13+)
20 | * Xcode command line tools & Xcode
21 | * Install with: `xcode-select --install` / [The mac app store](https://itunes.apple.com/us/app/xcode/id497799835)
22 | * [pipenv](https://pipenv.readthedocs.io/en/latest/)
23 | * Install with [homebrew](https://brew.sh): `brew install pipenv`
24 | * Optional: [cloc (Count Lines Of Code)](https://github.com/AlDanial/cloc)
25 | * Install with [homebrew](https://brew.sh): `brew install cloc`
26 |
27 | Depending on which project generator you plan to use, you will need to install at least one of the following:
28 |
29 | * [Buck](https://buckbuild.com/)
30 | * [Install instructions](https://buckbuild.com/setup/getting_started.html)
31 | * [Bazel](https://bazel.build/)
32 | * [Install instructions](https://docs.bazel.build/bazel-overview.html)
33 | * [CocoaPods](https://cocoapods.org/)
34 | * [Install instructions](https://cocoapods.org/#get_started)
35 |
36 | Then:
37 |
38 | * Download / git clone this project into a folder.
39 | * Run `pipenv install` to install the required python dependencies.
40 | * If you want to run unit tests or develop for this app, make sure to run `pipenv install --dev`
41 |
42 | ## How to Use
43 |
44 | After installing all required dependencies:
45 |
46 | See `pipenv run ./genproj.py -h` or `pipenv run ./mulisuite.py -h` for general help. Also take a look at the shell scripts in [examples/](examples/) to see examples on how to use these command line programs.
47 |
48 | Here a few quick examples:
49 |
50 | Generate a project using Buck:
51 | ```bash
52 | pipenv run ./genproj.py --output_directory "$HOME/Desktop/mockapp" \
53 | --project_generator_type "buck" \
54 | --blaze_module_path "/mockapp" \
55 | --gen_type flat \
56 | --swift_lines_of_code 150000
57 | ```
58 |
59 | Generate a project using Bazel:
60 | ```bash
61 | pipenv run ./genproj.py --output_directory "$HOME/Desktop/mockapp" \
62 | --project_generator_type "bazel" \
63 | --blaze_module_path "/mockapp" \
64 | --gen_type flat \
65 | --swift_lines_of_code 150000
66 | ```
67 |
68 | Generate a project using CocoaPods:
69 | ```bash
70 | pipenv run ./genproj.py --output_directory "$HOME/Desktop/mockapp" \
71 | --project_generator_type "cocoapods" \
72 | --gen_type flat \
73 | --swift_lines_of_code 150000
74 | ```
75 |
76 | You may also generate a project that includes both Swift and ObjC:
77 |
78 | ```bash
79 | pipenv run ./genproj.py --output_directory "$HOME/Desktop/mockapp" \
80 | --project_generator_type "cocoapods" \
81 | --gen_type flat \
82 | --swift_lines_of_code 100000 \
83 | --objc_lines_of_code 50000
84 | ```
85 |
86 | ```bash
87 | # You usually want to use `caffeinate` to prevent your computer
88 | # from going to sleep during a multi hour build test suite.
89 | caffeinate -s pipenv run \
90 | ./multisuite.py --log_dir "$HOME/Desktop/multisuite_build_results" \
91 | --app_gen_output_dir "$HOME/Desktop/multisuite_build_results/app_gen"
92 | ```
93 |
94 | You may also generate a project that matches your own project's dependency graph by using `--gen_type dot` parameter as well as supplying the location of the [`dot` file](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) that represents the graph:
95 |
96 | ```bash
97 | pipenv run ./genproj.py --output_directory "$HOME/Desktop/mockapp" \
98 | --project_generator_type "cocoapods" \
99 | --gen_type dot \
100 | --dot_file_path "$HOME/MyProject/my_project_graph.dot" \
101 | --dot_root_node_name "MyProject" \
102 | --swift_lines_of_code 150000
103 | ```
104 |
105 | Examples on how to generate a `dot` file:
106 |
107 |
108 | Using Buck:
109 | ```
110 | buck query \"deps(target)\" --dot > file.gv
111 | ```
112 |
113 | Using Bazel:
114 | ```
115 | bazel query "deps(target)" --output graph > graph.in
116 | ```
117 |
118 | Using CocoaPods:
119 |
120 | Install and use the [cocoapods-dependencies](https://github.com/segiddins/cocoapods-dependencies) plugin.
121 |
122 | You may also supply an optional JSON file to be used as a LOC map. This allows you to generate a project from your own dependency graph in which each generated module has proportional LOC to your original graph.
123 |
124 | ```bash
125 | pipenv run ./genproj.py --output_directory "$HOME/Desktop/mockapp" \
126 | --project_generator_type "cocoapods" \
127 | --gen_type dot \
128 | --dot_file_path "$HOME/MyProject/my_project_graph.dot" \
129 | --dot_root_node_name "MyProject" \
130 | --loc_json_file_path "$HOME/MyProject/cloc_mappings.json"
131 | ```
132 |
133 | Please note the format of the JSON file for LOC mappings must look like:
134 |
135 | ```json
136 | {
137 | "MyLibrary":500,
138 | "MyOtherLibrary":42
139 | }
140 | ```
141 |
142 | You may also specify a LOC mapping file that includes the language that you want to use for each module, for example:
143 |
144 | ```json
145 | {
146 | "MyLibrary": { "loc": 500, "language": "Objective-C" },
147 | "MyOtherLibrary":42
148 | }
149 | ```
150 |
151 | NOTE: All nodes found in your `dot` file must be present in your JSON LOC mappings file.
152 |
153 | Examples on how to get the CLOC:
154 |
155 | ```
156 | cloc file.swift --include-lang="Swift" --json
157 | ```
158 |
159 | Parse the JSON with your favorite language and read the `"code"` value from the `"SUM"` key.
160 |
161 | ## How to Contribute / Develop
162 |
163 | Take a look at [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md)!
164 |
165 | ## Project Status
166 |
167 | This project is stable and being incubated for long-term support.
168 |
169 | ## Licence
170 |
171 | This project is covered by the Apache License, Version 2.0:
172 |
173 | http://www.apache.org/licenses/LICENSE-2.0
174 |
175 | [LICENSE.txt](LICENSE.txt)
176 |
--------------------------------------------------------------------------------
/tests/test_statemanagement.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import os.path
18 | import tempfile
19 | import unittest
20 |
21 | import mock
22 | import testfixtures.popen
23 |
24 | from uberpoet.statemanagement import SettingsState, XcodeManager, XcodeVersion
25 |
26 | from .utils import read_file, write_file
27 |
28 |
29 | def stdoutize(s):
30 | return b'{}\n'.format(s)
31 |
32 |
33 | class TestXcodeManager(unittest.TestCase):
34 |
35 | def setUp(self):
36 | self.mock_popen = testfixtures.popen.MockPopen()
37 | self.r = testfixtures.Replacer()
38 | self.r.replace('subprocess.Popen', self.mock_popen)
39 | self.addCleanup(self.r.restore)
40 |
41 | def test_get_dirs(self):
42 | x = XcodeManager()
43 | fake_paths = ['/Applications/Xcode-beta.app', '/Applications/Xcode.app', '/Applications/Chess.app']
44 | expected_dirs = fake_paths[:2]
45 |
46 | with mock.patch('os.listdir') as mocked_listdir:
47 | mocked_listdir.return_value = fake_paths
48 | dirs = x.get_xcode_dirs()
49 | self.assertEqual(dirs, expected_dirs)
50 |
51 | def test_current_version(self):
52 | out = b'Xcode 10.0\nBuild version 10A255\n'
53 | self.mock_popen.set_command('xcodebuild -version', stdout=out)
54 | version, build = XcodeManager.get_current_xcode_version()
55 | self.assertEqual(version, '10.0')
56 | self.assertEqual(build, '10A255')
57 |
58 | def test_module_cache_dir(self):
59 | base = '/var/folders/sx/0zymnrm13ds1v5n9t4mjdqmr0000gp/C/'
60 | user = 'testuser'
61 | clangdir = 'org.llvm.clang.testuser'
62 | full_path = os.path.join(base, clangdir, 'ModuleCache')
63 |
64 | with mock.patch('getpass.getuser') as mock_getuser:
65 | mock_getuser.return_value = user
66 | self.mock_popen.set_command('getconf DARWIN_USER_CACHE_DIR', stdout=stdoutize(base))
67 | self.assertEqual(full_path, XcodeManager()._get_global_module_cache_dir())
68 |
69 | def test_clean_caches(self):
70 | self.mock_popen.set_default()
71 | XcodeManager().clean_caches()
72 | x = self.mock_popen.mock.method_calls
73 | self.assertEqual(len(x), 9)
74 |
75 | def test_discover_xcode_versions(self):
76 | # save_xcode_select / switch_xcode_version
77 | path = '/Applications/Xcode.app'
78 | self.mock_popen.set_command('xcode-select -p', stdout=b'{}\n'.format(path))
79 | self.mock_popen.set_command('sudo xcode-select -s ' + path)
80 |
81 | # get_current_xcode_version
82 | out = b'Xcode 10.0\nBuild version 10A255\n'
83 | self.mock_popen.set_command('xcodebuild -version', stdout=out)
84 |
85 | # get_xcode_dirs
86 | fake_paths = ['/Applications/Xcode.app']
87 | with mock.patch('os.listdir') as mocked_listdir:
88 | mocked_listdir.return_value = fake_paths
89 | versions = XcodeManager().discover_xcode_versions()
90 | expected = {('10.0', '10A255'): '/Applications/Xcode.app'}
91 | self.assertEqual(versions, expected)
92 |
93 |
94 | class TestSettingsState(unittest.TestCase):
95 |
96 | def setUp(self):
97 | self.mock_popen = testfixtures.popen.MockPopen()
98 | self.r = testfixtures.Replacer()
99 | self.r.replace('subprocess.Popen', self.mock_popen)
100 | self.addCleanup(self.r.restore)
101 |
102 | def test_buckconfig_restore(self):
103 | tmp = tempfile.gettempdir()
104 | conf_path = os.path.join(tmp, '.buckconfig.local')
105 | bak_conf_path = os.path.join(tmp, '.buckconfig.local.bak')
106 | config_content = 'a = b'
107 | new_config_content = '1 = 2'
108 | s = SettingsState(tmp)
109 |
110 | if os.path.isfile(conf_path):
111 | os.remove(conf_path)
112 | s.save_buckconfig_local()
113 | self.assertFalse(os.path.isfile(bak_conf_path)) # no file to save
114 |
115 | write_file(conf_path, config_content)
116 |
117 | for _ in xrange(4):
118 | s.save_buckconfig_local()
119 | self.assertTrue(os.path.isfile(bak_conf_path))
120 | self.assertEqual(read_file(bak_conf_path), config_content)
121 | write_file(conf_path, new_config_content)
122 | self.assertEqual(read_file(conf_path), new_config_content)
123 | s.restore_buckconfig_local()
124 | self.assertEqual(read_file(conf_path), config_content)
125 |
126 | def test_xcode(self):
127 | path = '/Applications/Xcode.10.0.0.10A255.app/Contents/Developer'
128 | self.mock_popen.set_command('xcode-select -p', stdout=stdoutize(path))
129 | self.mock_popen.set_command('sudo xcode-select -s ' + path)
130 | tmp = tempfile.gettempdir()
131 | s = SettingsState(tmp)
132 |
133 | s.save_xcode_select()
134 | self.assertEqual(s.select_path, path)
135 | s.restore_xcode_select()
136 |
137 |
138 | class TestXcodeVersion(unittest.TestCase):
139 |
140 | def test_choose_latest_major_versions_one_version(self):
141 | data = {('9.4.3', 'ASDF'): '/Applications/Xcode.9.4.3.app', ('10.0', 'QWERT'): '/Applications/Xcode-beta.app'}
142 |
143 | data2 = XcodeVersion.choose_latest_major_versions(data)
144 |
145 | self.assertEqual(data, data2)
146 |
147 | def test_repr(self):
148 | v = XcodeVersion('2.2.2', 'AAA')
149 | self.assertEqual(v.__repr__(), "XcodeVersion('2.2.2','AAA')")
150 |
151 | def test_choose_latest_major_versions_one_item(self):
152 | data = {
153 | ('9.4.3', 'ASDF'): '/Applications/Xcode.app',
154 | }
155 |
156 | data2 = XcodeVersion.choose_latest_major_versions(data)
157 |
158 | self.assertEqual(data, data2)
159 |
160 | def test_choose_latest_major_versions_multiple_majors(self):
161 | data = {
162 | ('9.5', 'ZZZZ'): '/Applications/Xcode.9.5.app',
163 | ('9.4.3', 'ASDF'): '/Applications/Xcode.9.4.3.app',
164 | ('9.2.3', 'JJJJ'): '/Applications/Xcode.9.2.3.app',
165 | ('10.0', 'AAAA'): '/Applications/Xcode-beta2.app',
166 | ('10.0', 'BBBB'): '/Applications/Xcode-beta.app',
167 | ('8.3.1', 'QWERT'): '/Applications/Xcode.8.3.1.app'
168 | }
169 |
170 | data_after = {
171 | ('9.5', 'ZZZZ'): '/Applications/Xcode.9.5.app',
172 | ('10.0', 'BBBB'): '/Applications/Xcode-beta.app',
173 | ('8.3.1', 'QWERT'): '/Applications/Xcode.8.3.1.app'
174 | }
175 |
176 | data2 = XcodeVersion.choose_latest_major_versions(data)
177 |
178 | self.assertEqual(data_after, data2)
179 |
180 | def test_xcode_equality(self):
181 | a = XcodeVersion('1.2.3', 'AAA')
182 | a2 = XcodeVersion('1.2.3', 'AAA')
183 | az = XcodeVersion('1.2.3', 'ZZZ')
184 | b = XcodeVersion('2.2.3', 'AAA')
185 | a124 = XcodeVersion('1.2.4', 'AAA')
186 | a125 = XcodeVersion('1.2.5', 'AAA')
187 |
188 | self.assertEqual(a, a2)
189 | self.assertEqual(a, a)
190 | self.assertNotEqual(a, az)
191 | self.assertNotEqual(a, b)
192 | self.assertNotEqual(b, a2)
193 | self.assertNotEqual(b, az)
194 |
195 | self.assertFalse(a124.__eq__(a125))
196 | self.assertFalse(a.__eq__(az))
197 | self.assertTrue(az.__gt__(a))
198 | self.assertFalse(a.__gt__(az))
199 | self.assertTrue(b.__gt__(a))
200 |
--------------------------------------------------------------------------------
/uberpoet/commandlineutil.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import ConfigParser
18 | import json
19 | import logging
20 | import os
21 | import shutil
22 | import subprocess
23 | from os.path import join
24 |
25 | from toposort import toposort_flatten
26 |
27 | from . import dotreader
28 | from .cpulogger import CPULog
29 | from .filegen import Language
30 | from .moduletree import ModuleGenType, ModuleNode
31 | from .util import bool_xor
32 |
33 |
34 | class AppGenerationConfig(object):
35 |
36 | def __init__(self,
37 | module_count=0,
38 | big_module_count=0,
39 | small_module_count=0,
40 | swift_lines_of_code=0,
41 | objc_lines_of_code=0,
42 | app_layer_count=0,
43 | dot_file_path='',
44 | dot_root_node_name='',
45 | loc_json_file_path=''):
46 | self.module_count = module_count
47 | self.big_module_count = big_module_count
48 | self.small_module_count = small_module_count
49 | self.swift_lines_of_code = swift_lines_of_code
50 | self.objc_lines_of_code = objc_lines_of_code
51 | self.app_layer_count = app_layer_count
52 | self.dot_file_path = dot_file_path
53 | self.dot_root_node_name = dot_root_node_name
54 | self.loc_json_file_path = loc_json_file_path
55 |
56 | def pull_from_args(self, args):
57 | self.validate_app_gen_options(args)
58 | self.module_count = args.module_count
59 | self.big_module_count = args.big_module_count
60 | self.small_module_count = args.small_module_count
61 | self.swift_lines_of_code = args.swift_lines_of_code
62 | self.objc_lines_of_code = args.objc_lines_of_code
63 | self.app_layer_count = args.app_layer_count
64 | self.dot_file_path = args.dot_file_path
65 | self.dot_root_node_name = args.dot_root_node_name
66 | self.loc_json_file_path = args.loc_json_file_path
67 |
68 | @staticmethod
69 | def add_app_gen_options(parser):
70 | app = parser.add_argument_group('Mock app generation options')
71 | app.add_argument(
72 | '--module_count', default=100, type=int, help="How many modules should be in a normal mock app type."),
73 | app.add_argument(
74 | '--big_module_count',
75 | default=3,
76 | type=int,
77 | help="How many big modules should be in a big/small mock app type."),
78 | app.add_argument(
79 | '--small_module_count',
80 | default=50,
81 | type=int,
82 | help="How many small modules should be in a big/small mock app type."),
83 | app.add_argument(
84 | '--swift_lines_of_code',
85 | default=1500000, # 1.5 million lines of code
86 | type=int,
87 | help="Approximately how many lines of Swift code each mock app should have."),
88 | app.add_argument(
89 | '--objc_lines_of_code',
90 | default=0,
91 | type=int,
92 | help="Approximately how many lines of ObjC code each mock app should have."),
93 | app.add_argument(
94 | '--app_layer_count',
95 | default=10,
96 | type=int,
97 | help='How many module layers there should be in the layered mock app type.')
98 |
99 | dot = parser.add_argument_group('Dot file mock app config')
100 | dot.add_argument(
101 | '--dot_file_path',
102 | default='',
103 | type=str,
104 | help="The path to the dot file to create a mock module graph from. This dot file for Buck can be "
105 | "created like so: `buck query \"deps(target)\" --dot > file.gv`. Alternatively, you may use your own"
106 | "means to generate it for different project types.")
107 | dot.add_argument(
108 | '--dot_root_node_name',
109 | default='',
110 | type=str,
111 | help="The name of the root application node of the dot file, such as 'App'.")
112 | parser.add_argument(
113 | '--loc_json_file_path',
114 | default='',
115 | type=str,
116 | help="A JSON file used to provide module LOC data. Only used when dot graph type is used to create "
117 | "modules with proportional LOC. You may generate this file using `cloc` or another tool like `tokei`."
118 | " The format of the file is expected to contain each module name as a key with a value denoting the LOC.")
119 |
120 | @staticmethod
121 | def validate_app_gen_options(args):
122 | if bool_xor(args.dot_file_path, args.dot_root_node_name):
123 | logging.info('dot_file_path: "%s" dot_root_node_name: "%s"', args.dot_file_path, args.dot_root_node_name)
124 | raise ValueError('If you specify a dot file config option, you must also specify a root node name using '
125 | '\"dot_root_node_name\".')
126 | if args.loc_json_file_path and args.gen_type != ModuleGenType.dot:
127 | logging.info('loc_json_file_path: "%s"', args.loc_json_file_path)
128 | raise ValueError('If you specify \"loc_json_file_path\", you must also specify a dot graph style.')
129 |
130 |
131 | def gen_graph(gen_type, config):
132 | # app_node, node_list = None, None
133 | modules_per_layer = config.module_count / config.app_layer_count
134 |
135 | if gen_type == ModuleGenType.flat:
136 | app_node, node_list = ModuleNode.gen_flat_graph(config.module_count)
137 | elif gen_type == ModuleGenType.bs_flat:
138 | app_node, node_list = ModuleNode.gen_flat_big_small_graph(config.big_module_count, config.small_module_count)
139 | elif gen_type == ModuleGenType.layered:
140 | app_node, node_list = ModuleNode.gen_layered_graph(config.app_layer_count, modules_per_layer)
141 | elif gen_type == ModuleGenType.bs_layered:
142 | app_node, node_list = ModuleNode.gen_layered_big_small_graph(config.big_module_count, config.small_module_count)
143 | elif gen_type == ModuleGenType.dot and config.dot_file_path and config.dot_root_node_name:
144 | logging.info("Reading dot file: %s", config.dot_file_path)
145 | app_node, parsed_node_list = dotreader.DotFileReader().read_dot_file(config.dot_file_path,
146 | config.dot_root_node_name)
147 | node_graph = {n: set(n.deps) for n in parsed_node_list}
148 | node_list = toposort_flatten(node_graph)
149 | else:
150 | logging.error("Unexpected argument set, aborting.")
151 | item_list = ', '.join(ModuleGenType.enum_list())
152 | logging.error("Choose from ({}) module count: {} dot path: {} ".format(item_list, config.module_count,
153 | config.dot_path))
154 | raise ValueError("Invalid Arguments")
155 |
156 | return app_node, node_list
157 |
158 |
159 | def del_old_output_dir(output_directory):
160 | if os.path.isdir(output_directory):
161 | logging.warning("Deleting old mock app directory %s", output_directory)
162 | shutil.rmtree(output_directory)
163 |
164 |
165 | def make_custom_buckconfig_local(buckconfig_path):
166 | logging.warn('Overwriting .buckconfig.local file at: %s', buckconfig_path)
167 | config = ConfigParser.RawConfigParser()
168 | config.add_section('project')
169 | config.set('project', 'ide_force_kill', 'never')
170 | config.add_section('parser')
171 | config.set('parser', 'polyglot_parsing_enabled', 'true')
172 | config.set('parser', 'default_build_file_syntax', 'SKYLARK')
173 |
174 | with open(buckconfig_path, 'w') as buckconfig:
175 | config.write(buckconfig)
176 |
177 |
178 | def count_loc(code_path, language=Language.SWIFT):
179 | """Returns the number of lines of code in `code_path` using cloc. If cloc is not
180 | on your system or there is an error, then it returns -1"""
181 | try:
182 | logging.info('Counting lines of code in %s', code_path)
183 | raw_json_out = subprocess.check_output(['cloc', '--quiet', '--json', code_path])
184 | except OSError:
185 | logging.warning("You do not have cloc installed, skipping line counting.")
186 | return -1
187 |
188 | json_out = json.loads(raw_json_out)
189 | language_loc = json_out.get(language, {}).get('code', 0)
190 | if not language_loc:
191 | logging.error('Unexpected cloc output "%s"', raw_json_out)
192 | raise ValueError('cloc did not give a correct value')
193 | return language_loc
194 |
195 |
196 | def apply_cpu_to_traces(build_trace_path, cpu_logger, time_cutoff=None):
197 | logging.info('Applying CPU info to traces in %s', build_trace_path)
198 | cpu_logs = cpu_logger.process_log()
199 | trace_paths = [join(build_trace_path, f) for f in os.listdir(build_trace_path) if f.endswith('trace')]
200 | for trace_path in trace_paths:
201 | if time_cutoff and os.path.getmtime(trace_path) < time_cutoff:
202 | continue
203 | with open(trace_path, 'r') as trace_file:
204 | traces = json.load(trace_file)
205 | new_traces = CPULog.apply_log_to_trace(cpu_logs, traces)
206 | with open(trace_path + '.json', 'w') as new_trace_file:
207 | json.dump(new_traces, new_trace_file)
208 |
--------------------------------------------------------------------------------
/tests/fixtures/loc_mappings.json:
--------------------------------------------------------------------------------
1 | {
2 | "DotReaderLib0": { "loc": 180, "language": "Objective-C" },
3 | "DotReaderLib1": 260,
4 | "DotReaderLib2": 210,
5 | "DotReaderLib3": 390,
6 | "DotReaderLib4": 220,
7 | "DotReaderLib5": 331,
8 | "DotReaderLib6": 17,
9 | "DotReaderLib7": 268,
10 | "DotReaderLib8": 496,
11 | "DotReaderLib9": 471,
12 | "DotReaderLib10": 129,
13 | "DotReaderLib11": 376,
14 | "DotReaderLib12": 330,
15 | "DotReaderLib13": 93,
16 | "DotReaderLib14": 342,
17 | "DotReaderLib15": 244,
18 | "DotReaderLib16": 419,
19 | "DotReaderLib17": 283,
20 | "DotReaderLib18": 341,
21 | "DotReaderLib19": 215,
22 | "DotReaderLib20": 161,
23 | "DotReaderLib21": 203,
24 | "DotReaderLib22": 239,
25 | "DotReaderLib23": 153,
26 | "DotReaderLib24": 355,
27 | "DotReaderLib25": 487,
28 | "DotReaderLib26": 287,
29 | "DotReaderLib27": 351,
30 | "DotReaderLib28": 333,
31 | "DotReaderLib29": 8,
32 | "DotReaderLib30": 336,
33 | "DotReaderLib31": 399,
34 | "DotReaderLib32": 12,
35 | "DotReaderLib33": 361,
36 | "DotReaderLib34": 394,
37 | "DotReaderLib35": 360,
38 | "DotReaderLib36": 341,
39 | "DotReaderLib37": 159,
40 | "DotReaderLib38": 387,
41 | "DotReaderLib39": 315,
42 | "DotReaderLib40": 165,
43 | "DotReaderLib41": 483,
44 | "DotReaderLib42": 379,
45 | "DotReaderLib43": 106,
46 | "DotReaderLib44": 230,
47 | "DotReaderLib45": 153,
48 | "DotReaderLib46": 155,
49 | "DotReaderLib47": 85,
50 | "DotReaderLib48": 407,
51 | "DotReaderLib49": 332,
52 | "DotReaderLib50": 241,
53 | "DotReaderLib51": 205,
54 | "DotReaderLib52": 14,
55 | "DotReaderLib53": 441,
56 | "DotReaderLib54": 146,
57 | "DotReaderLib55": 8,
58 | "DotReaderLib56": 25,
59 | "DotReaderLib57": 54,
60 | "DotReaderLib58": 143,
61 | "DotReaderLib59": 54,
62 | "DotReaderLib60": 14,
63 | "DotReaderLib61": 295,
64 | "DotReaderLib62": 181,
65 | "DotReaderLib63": 147,
66 | "DotReaderLib64": 439,
67 | "DotReaderLib65": 198,
68 | "DotReaderLib66": 76,
69 | "DotReaderLib67": 223,
70 | "DotReaderLib68": 395,
71 | "DotReaderLib69": 241,
72 | "DotReaderLib70": 214,
73 | "DotReaderLib71": 156,
74 | "DotReaderLib72": 216,
75 | "DotReaderLib73": 162,
76 | "DotReaderLib74": 245,
77 | "DotReaderLib75": 169,
78 | "DotReaderLib76": 364,
79 | "DotReaderLib77": 146,
80 | "DotReaderLib78": 229,
81 | "DotReaderLib79": 119,
82 | "DotReaderLib80": 287,
83 | "DotReaderLib81": 432,
84 | "DotReaderLib82": 101,
85 | "DotReaderLib83": 168,
86 | "DotReaderLib84": 99,
87 | "DotReaderLib85": 362,
88 | "DotReaderLib86": 341,
89 | "DotReaderLib87": 307,
90 | "DotReaderLib88": 200,
91 | "DotReaderLib89": 216,
92 | "DotReaderLib90": 17,
93 | "DotReaderLib91": 283,
94 | "DotReaderLib92": 46,
95 | "DotReaderLib93": 400,
96 | "DotReaderLib94": 358,
97 | "DotReaderLib95": 344,
98 | "DotReaderLib96": 384,
99 | "DotReaderLib97": 100,
100 | "DotReaderLib98": 308,
101 | "DotReaderLib99": 308,
102 | "DotReaderLib100": 361,
103 | "DotReaderLib101": 18,
104 | "DotReaderLib102": 462,
105 | "DotReaderLib103": 99,
106 | "DotReaderLib104": 453,
107 | "DotReaderLib105": 352,
108 | "DotReaderLib106": 228,
109 | "DotReaderLib107": 43,
110 | "DotReaderLib108": 452,
111 | "DotReaderLib109": 265,
112 | "DotReaderLib110": 10,
113 | "DotReaderLib111": 350,
114 | "DotReaderLib112": 355,
115 | "DotReaderLib113": 48,
116 | "DotReaderLib114": 124,
117 | "DotReaderLib115": 466,
118 | "DotReaderLib116": 161,
119 | "DotReaderLib117": 199,
120 | "DotReaderLib118": 28,
121 | "DotReaderLib119": 484,
122 | "DotReaderLib120": 149,
123 | "DotReaderLib121": 416,
124 | "DotReaderLib122": 174,
125 | "DotReaderLib123": 213,
126 | "DotReaderLib124": 205,
127 | "DotReaderLib125": 121,
128 | "DotReaderLib126": 335,
129 | "DotReaderLib127": 86,
130 | "DotReaderLib128": 42,
131 | "DotReaderLib129": 82,
132 | "DotReaderLib130": 184,
133 | "DotReaderLib131": 17,
134 | "DotReaderLib132": 426,
135 | "DotReaderLib133": 231,
136 | "DotReaderLib134": 406,
137 | "DotReaderLib135": 21,
138 | "DotReaderLib136": 364,
139 | "DotReaderLib137": 486,
140 | "DotReaderLib138": 15,
141 | "DotReaderLib139": 422,
142 | "DotReaderLib140": 30,
143 | "DotReaderLib141": 495,
144 | "DotReaderLib142": 386,
145 | "DotReaderLib143": 340,
146 | "DotReaderLib144": 205,
147 | "DotReaderLib145": 75,
148 | "DotReaderLib146": 45,
149 | "DotReaderLib147": 55,
150 | "DotReaderLib148": 310,
151 | "DotReaderLib149": 490,
152 | "DotReaderLib150": 45,
153 | "DotReaderLib151": 5,
154 | "DotReaderLib152": 194,
155 | "DotReaderLib153": 466,
156 | "DotReaderLib154": 58,
157 | "DotReaderLib155": 423,
158 | "DotReaderLib156": 234,
159 | "DotReaderLib157": 224,
160 | "DotReaderLib158": 240,
161 | "DotReaderLib159": 121,
162 | "DotReaderLib160": 384,
163 | "DotReaderLib161": 49,
164 | "DotReaderLib162": 240,
165 | "DotReaderLib163": 167,
166 | "DotReaderLib164": 227,
167 | "DotReaderLib165": 380,
168 | "DotReaderLib166": 65,
169 | "DotReaderLib167": 235,
170 | "DotReaderLib168": 251,
171 | "DotReaderLib169": 15,
172 | "DotReaderLib170": 118,
173 | "DotReaderLib171": 430,
174 | "DotReaderLib172": 383,
175 | "DotReaderLib173": 113,
176 | "DotReaderLib174": 20,
177 | "DotReaderLib175": 167,
178 | "DotReaderLib176": 7,
179 | "DotReaderLib177": 74,
180 | "DotReaderLib178": 159,
181 | "DotReaderLib179": 109,
182 | "DotReaderLib180": 403,
183 | "DotReaderLib181": 264,
184 | "DotReaderLib182": 37,
185 | "DotReaderLib183": 172,
186 | "DotReaderLib184": 209,
187 | "DotReaderLib185": 449,
188 | "DotReaderLib186": 143,
189 | "DotReaderLib187": 384,
190 | "DotReaderLib188": 26,
191 | "DotReaderLib189": 35,
192 | "DotReaderLib190": 320,
193 | "DotReaderLib191": 266,
194 | "DotReaderLib192": 401,
195 | "DotReaderLib193": 333,
196 | "DotReaderLib194": 318,
197 | "DotReaderLib195": 402,
198 | "DotReaderLib196": 315,
199 | "DotReaderLib197": 77,
200 | "DotReaderLib198": 323,
201 | "DotReaderLib199": 488,
202 | "DotReaderLib200": 30,
203 | "DotReaderLib201": 82,
204 | "DotReaderLib202": 153,
205 | "DotReaderLib203": 177,
206 | "DotReaderLib204": 397,
207 | "DotReaderLib205": 417,
208 | "DotReaderLib206": 350,
209 | "DotReaderLib207": 240,
210 | "DotReaderLib208": 425,
211 | "DotReaderLib209": 439,
212 | "DotReaderLib210": 204,
213 | "DotReaderLib211": 400,
214 | "DotReaderLib212": 341,
215 | "DotReaderLib213": 425,
216 | "DotReaderLib214": 316,
217 | "DotReaderLib215": 438,
218 | "DotReaderLib216": 11,
219 | "DotReaderLib217": 86,
220 | "DotReaderLib218": 489,
221 | "DotReaderLib219": 355,
222 | "DotReaderLib220": 439,
223 | "DotReaderLib221": 243,
224 | "DotReaderLib222": 453,
225 | "DotReaderLib223": 382,
226 | "DotReaderLib224": 355,
227 | "DotReaderLib225": 298,
228 | "DotReaderLib226": 305,
229 | "DotReaderLib227": 48,
230 | "DotReaderLib228": 86,
231 | "DotReaderLib229": 1,
232 | "DotReaderLib230": 90,
233 | "DotReaderLib231": 225,
234 | "DotReaderLib232": 116,
235 | "DotReaderLib233": 306,
236 | "DotReaderLib234": 447,
237 | "DotReaderLib235": 299,
238 | "DotReaderLib236": 84,
239 | "DotReaderLib237": 63,
240 | "DotReaderLib238": 395,
241 | "DotReaderLib239": 155,
242 | "DotReaderLib240": 488,
243 | "DotReaderLib241": 163,
244 | "DotReaderLib242": 477,
245 | "DotReaderLib243": 11,
246 | "DotReaderLib244": 87,
247 | "DotReaderLib245": 51,
248 | "DotReaderLib246": 414,
249 | "DotReaderLib247": 5,
250 | "DotReaderLib248": 198,
251 | "DotReaderLib249": 201,
252 | "DotReaderLib250": 483,
253 | "DotReaderLib251": 255,
254 | "DotReaderLib252": 401,
255 | "DotReaderLib253": 158,
256 | "DotReaderLib254": 414,
257 | "DotReaderLib255": 14,
258 | "DotReaderLib256": 258,
259 | "DotReaderLib257": 296,
260 | "DotReaderLib258": 118,
261 | "DotReaderLib259": 117,
262 | "DotReaderLib260": 212,
263 | "DotReaderLib261": 454,
264 | "DotReaderLib262": 199,
265 | "DotReaderLib263": 335,
266 | "DotReaderLib264": 1,
267 | "DotReaderLib265": 201,
268 | "DotReaderLib266": 141,
269 | "DotReaderLib267": 342,
270 | "DotReaderLib268": 27,
271 | "DotReaderLib269": 113,
272 | "DotReaderLib270": 4,
273 | "DotReaderLib271": 305,
274 | "DotReaderLib272": 194,
275 | "DotReaderLib273": 369,
276 | "DotReaderLib274": 1,
277 | "DotReaderLib275": 345,
278 | "DotReaderLib276": 313,
279 | "DotReaderLib277": 358,
280 | "DotReaderLib278": 221,
281 | "DotReaderLib279": 60,
282 | "DotReaderLib280": 199,
283 | "DotReaderLib281": 489,
284 | "DotReaderLib282": 320,
285 | "DotReaderLib283": 378,
286 | "DotReaderLib284": 100,
287 | "DotReaderLib285": 181,
288 | "DotReaderLib286": 159,
289 | "DotReaderLib287": 442,
290 | "DotReaderLib288": 248,
291 | "DotReaderLib289": 73,
292 | "DotReaderLib290": 382,
293 | "DotReaderLib291": 209,
294 | "DotReaderLib292": 217,
295 | "DotReaderLib293": 28,
296 | "DotReaderLib294": 346,
297 | "DotReaderLib295": 421,
298 | "DotReaderLib296": 416,
299 | "DotReaderLib297": 279,
300 | "DotReaderLib298": 430,
301 | "DotReaderLib299": 451,
302 | "DotReaderLib300": 196,
303 | "DotReaderLib301": 303,
304 | "DotReaderLib302": 308,
305 | "DotReaderLib303": 385,
306 | "DotReaderLib304": 145,
307 | "DotReaderLib305": 371,
308 | "DotReaderLib306": 83,
309 | "DotReaderLib307": 59,
310 | "DotReaderLib308": 127,
311 | "DotReaderLib309": 317,
312 | "DotReaderLib310": 45,
313 | "DotReaderLib311": 345,
314 | "DotReaderLib312": 270,
315 | "DotReaderLib313": 380,
316 | "DotReaderLib314": 67,
317 | "DotReaderLib315": 119,
318 | "DotReaderLib316": 448,
319 | "DotReaderLib317": 455,
320 | "DotReaderLib318": 253,
321 | "DotReaderLib319": 302,
322 | "DotReaderLib320": 463,
323 | "DotReaderLib321": 171,
324 | "DotReaderLib322": 324,
325 | "DotReaderLib323": 459,
326 | "DotReaderLib324": 179,
327 | "DotReaderLib325": 345,
328 | "DotReaderLib326": 471,
329 | "DotReaderLib327": 417,
330 | "DotReaderLib328": 283,
331 | "DotReaderLib329": 147,
332 | "DotReaderLib330": 387,
333 | "DotReaderLib331": 204,
334 | "DotReaderLib332": 415,
335 | "DotReaderLib333": 265,
336 | "DotReaderLib334": 58,
337 | "DotReaderLib335": 426,
338 | "DotReaderLib336": 476
339 | }
340 |
--------------------------------------------------------------------------------
/tests/test_buck_integration.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import os
18 | import tempfile
19 | import unittest
20 | from os.path import join
21 |
22 | import mock
23 | import testfixtures.popen
24 | from testfixtures.popen import MockPopen, PopenBehaviour
25 |
26 | from uberpoet.filegen import Language
27 | from uberpoet.genproj import GenProjCommandLine
28 | from uberpoet.multisuite import CommandLineMultisuite
29 |
30 | from .utils import integration_test, read_file
31 |
32 |
33 | class TestBuckIntegration(unittest.TestCase):
34 |
35 | def verify_genproj(self, app_path, dir_file_count, swift_file_count, objc_file_count):
36 | main_path = join(app_path, 'App')
37 |
38 | # Top level dir
39 | contents = os.listdir(app_path)
40 | self.assertGreater(len(contents), 0)
41 | self.assertEqual(len(contents), dir_file_count)
42 |
43 | swift_file_contents = []
44 | objc_file_contents = []
45 |
46 | for dirpath, dirnames, filenames in os.walk(app_path):
47 | for f in filenames:
48 | ext = os.path.splitext(f)[1]
49 | if ext == '.swift':
50 | swift_file_contents.append(os.path.join(dirpath, f))
51 | elif ext == '.h' or ext == '.m':
52 | objc_file_contents.append(os.path.join(dirpath, f))
53 |
54 | self.assertEqual(len(swift_file_contents), swift_file_count)
55 | self.assertEqual(len(objc_file_contents), objc_file_count)
56 |
57 | # App dir
58 | self.assertIn('App', contents)
59 | app_contents = os.listdir(main_path)
60 | self.assertGreater(len(app_contents), 0)
61 | for file_name in ['BUCK', 'AppDelegate.swift', 'Info.plist']:
62 | self.assertIn(file_name, app_contents)
63 | with open(join(main_path, file_name), 'r') as f:
64 | self.assertGreater(len(f.read()), 0)
65 | # TODO actually verify generated code?
66 |
67 | def verify_lib(self, app_path, lib_name, language=Language.SWIFT):
68 | lib_path = join(app_path, lib_name, 'Sources')
69 |
70 | lib_contents = os.listdir(lib_path)
71 | self.assertGreater(len(lib_contents), 0)
72 |
73 | if language == Language.SWIFT:
74 | files = list(['File0.swift'])
75 | elif language == Language.OBJC:
76 | files = list(['File0.h', 'File0.m'])
77 |
78 | for f in files:
79 | self.assertIn(f, lib_contents)
80 | with open(join(lib_path, f), 'r') as f:
81 | self.assertGreater(len(f.read()), 0)
82 | # TODO actually verify generated code?
83 |
84 | @integration_test
85 | def test_flat_genproj(self):
86 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
87 | args = [
88 | "--output_directory", app_path, "--blaze_module_path", "/apps/mockapp", "--gen_type", "flat",
89 | "--swift_lines_of_code", "150000"
90 | ]
91 | command = GenProjCommandLine()
92 | command.main(args)
93 |
94 | self.verify_genproj(app_path, 104, 901, 0)
95 | self.verify_lib(app_path, 'MockLib53')
96 |
97 | @integration_test
98 | def test_genproj_with_objc(self):
99 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
100 | args = [
101 | "--output_directory", app_path, "--blaze_module_path", "/apps/mockapp", "--gen_type", "flat",
102 | "--swift_lines_of_code", "0",
103 | "--objc_lines_of_code", "150000"
104 | ]
105 | command = GenProjCommandLine()
106 | command.main(args)
107 | # 1 Swift file count expected due to main.swift for the app target.
108 | self.verify_genproj(app_path, 104, 1, 2200)
109 | self.verify_lib(app_path, 'MockLib53', Language.OBJC)
110 |
111 | @integration_test
112 | def test_dot_genproj(self):
113 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
114 |
115 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
116 | args = [
117 | "--output_directory", app_path, "--blaze_module_path", "/apps/mockapp", "--gen_type", "dot",
118 | "--swift_lines_of_code", "150000", "--dot_file", test_fixture_path, "--dot_root", "DotReaderMainModule"
119 | ]
120 | command = GenProjCommandLine()
121 | command.main(args)
122 |
123 | self.verify_genproj(app_path, 341, 675, 0)
124 | self.verify_lib(app_path, 'DotReaderLib17')
125 |
126 | @integration_test
127 | def test_dot_genproj_with_loc_mappings(self):
128 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
129 |
130 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
131 | loc_test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'loc_mappings.json')
132 | args = [
133 | "--output_directory", app_path, "--blaze_module_path", "/apps/mockapp", "--gen_type", "dot",
134 | "--dot_file", test_fixture_path, "--dot_root", "DotReaderMainModule",
135 | "--loc_json_file_path", loc_test_fixture_path
136 | ]
137 | command = GenProjCommandLine()
138 | command.main(args)
139 |
140 | self.verify_genproj(app_path, 342, 484, 2)
141 | self.verify_lib(app_path, 'DotReaderLib17')
142 |
143 | @integration_test
144 | def test_flat_multisuite(self):
145 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
146 | app_path = join(root_path, 'apps', 'mockapp')
147 | log_path = join(root_path, 'logs')
148 | args = ["--log_dir", log_path, "--app_gen_output_dir", root_path, "--test_build_only", "--skip_xcode_build"]
149 | command = CommandLineMultisuite()
150 | command.main(args)
151 | self.assertGreater(os.listdir(app_path), 0)
152 | self.verify_genproj(app_path, 103, 601, 0)
153 | self.verify_lib(app_path, 'MockLib53')
154 |
155 | @integration_test
156 | def test_flat_multisuite_mocking_calls(self):
157 | test_cloc_out_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'cloc_out.json')
158 | cloc_out = read_file(test_cloc_out_path)
159 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
160 | app_path = join(root_path, 'apps', 'mockapp')
161 | log_path = join(root_path, 'logs')
162 | args = [
163 | "--log_dir", log_path, "--app_gen_output_dir", root_path, "--test_build_only", "--switch_xcode_versions",
164 | "--full_clean"
165 | ]
166 |
167 | # we need the unused named variable for mocking purposes
168 | # noinspection PyUnusedLocal
169 | def command_callable(command, stdin):
170 | if 'cloc' in command:
171 | return PopenBehaviour(stdout=cloc_out)
172 | elif 'xcodebuild -version' in command:
173 | return PopenBehaviour(stdout=b'Xcode 10.0\nBuild version 10A255\n')
174 | return PopenBehaviour(stdout=b'test_out', stderr=b'test_error')
175 |
176 | with testfixtures.Replacer() as rep:
177 | mock_popen = MockPopen()
178 | rep.replace('subprocess.Popen', mock_popen)
179 | mock_popen.set_default(behaviour=command_callable)
180 |
181 | with mock.patch('distutils.spawn.find_executable') as mock_find:
182 | mock_find.return_value = '/bin/ls' # A non empty return value basically means "I found that executable"
183 | CommandLineMultisuite().main(args)
184 | self.assertGreater(os.listdir(app_path), 0)
185 | self.verify_genproj(app_path, 103, 601, 0)
186 | self.verify_lib(app_path, 'MockLib53')
187 |
188 | @integration_test
189 | def test_dot_multisuite(self):
190 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
191 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
192 | app_path = join(root_path, 'apps', 'mockapp')
193 | log_path = join(root_path, 'logs')
194 | args = [
195 | "--log_dir", log_path, "--app_gen_output_dir", root_path, "--dot_file", test_fixture_path, "--dot_root",
196 | "DotReaderMainModule", "--skip_xcode_build", "--test_build_only"
197 | ]
198 | command = CommandLineMultisuite()
199 | command.main(args)
200 | self.assertGreater(os.listdir(app_path), 0)
201 | self.verify_genproj(app_path, 340, 338, 0)
202 | self.verify_lib(app_path, 'DotReaderLib17')
203 |
204 | @integration_test
205 | def test_all_multisuite(self):
206 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
207 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
208 | app_path = join(root_path, 'apps', 'mockapp')
209 | log_path = join(root_path, 'logs')
210 | args = [
211 | "--log_dir", log_path, "--app_gen_output_dir", root_path, "--dot_file", test_fixture_path, "--dot_root",
212 | "DotReaderMainModule", "--skip_xcode_build", "--swift_lines_of_code", "150000"
213 | ]
214 | command = CommandLineMultisuite()
215 | command.main(args)
216 | self.assertGreater(os.listdir(app_path), 0)
217 |
218 | # Note we are assuming that the last project to be generated is the dot project.
219 | # If you change the order of project generation, make this match whatever is the new 'last project'
220 | # It's a bit fragile, but it's better than not verifying anything currently
221 | self.verify_genproj(app_path, 340, 675, 0)
222 | self.verify_lib(app_path, 'DotReaderLib17')
223 |
--------------------------------------------------------------------------------
/tests/test_cp_integration.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import os
18 | import tempfile
19 | import unittest
20 | from os.path import join
21 |
22 | import mock
23 | import testfixtures.popen
24 | from testfixtures.popen import MockPopen, PopenBehaviour
25 |
26 | from uberpoet.filegen import Language
27 | from uberpoet.genproj import GenProjCommandLine
28 | from uberpoet.multisuite import CommandLineMultisuite
29 |
30 | from .utils import integration_test, read_file
31 |
32 |
33 | class TestCocoaPodsIntegration(unittest.TestCase):
34 | def verify_genproj(self, app_path, dir_file_count, swift_file_count, objc_file_count):
35 | main_path = join(app_path, 'App')
36 |
37 | # Top level dir
38 | contents = os.listdir(app_path)
39 | self.assertGreater(len(contents), 0)
40 | self.assertEqual(len(contents), dir_file_count)
41 |
42 | swift_file_contents = []
43 | objc_file_contents = []
44 |
45 | for dirpath, dirnames, filenames in os.walk(app_path):
46 | for f in filenames:
47 | ext = os.path.splitext(f)[1]
48 | if ext == '.swift':
49 | swift_file_contents.append(os.path.join(dirpath, f))
50 | elif ext == '.h' or ext == '.m':
51 | objc_file_contents.append(os.path.join(dirpath, f))
52 |
53 | self.assertEqual(len(swift_file_contents), swift_file_count)
54 | self.assertEqual(len(objc_file_contents), objc_file_count)
55 |
56 | # App dir
57 | self.assertIn('App', contents)
58 | app_contents = os.listdir(main_path)
59 | self.assertGreater(len(app_contents), 0)
60 | for file_name in ['AppContainer.podspec', 'AppDelegate.swift', 'Info.plist']:
61 | self.assertIn(file_name, app_contents)
62 | with open(join(main_path, file_name), 'r') as f:
63 | self.assertGreater(len(f.read()), 0)
64 | # TODO actually verify generated code?
65 |
66 | def verify_lib(self, app_path, lib_name, language=Language.SWIFT):
67 | lib_path = join(app_path, lib_name, 'Sources')
68 |
69 | lib_contents = os.listdir(lib_path)
70 | self.assertGreater(len(lib_contents), 0)
71 |
72 | if language == Language.SWIFT:
73 | files = list(['File0.swift'])
74 | elif language == Language.OBJC:
75 | files = list(['File0.h', 'File0.m'])
76 |
77 | for f in files:
78 | self.assertIn(f, lib_contents)
79 | with open(join(lib_path, f), 'r') as f:
80 | self.assertGreater(len(f.read()), 0)
81 | # TODO actually verify generated code?
82 |
83 | @integration_test
84 | def test_flat_genproj(self):
85 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
86 | args = [
87 | "--output_directory", app_path, "--project_generator_type", "cocoapods", "--gen_type", "flat",
88 | "--swift_lines_of_code", "150000"
89 | ]
90 | command = GenProjCommandLine()
91 | command.main(args)
92 |
93 | self.verify_genproj(app_path, 104, 902, 0)
94 | self.verify_lib(app_path, 'MockLib53')
95 |
96 | @integration_test
97 | def test_genproj_with_objc(self):
98 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
99 | args = [
100 | "--output_directory", app_path, "--project_generator_type", "cocoapods", "--gen_type", "flat",
101 | "--swift_lines_of_code", "0", "--objc_lines_of_code", "150000"
102 | ]
103 | command = GenProjCommandLine()
104 | command.main(args)
105 |
106 | # 2 Swift file count expected due to main.swift and dummy.swift files for the app target.
107 | self.verify_genproj(app_path, 104, 2, 2200)
108 | self.verify_lib(app_path, 'MockLib53', Language.OBJC)
109 |
110 | @integration_test
111 | def test_dot_genproj(self):
112 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
113 |
114 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
115 | args = [
116 | "--output_directory", app_path, "--project_generator_type", "cocoapods", "--gen_type", "dot",
117 | "--swift_lines_of_code", "150000", "--dot_file", test_fixture_path, "--dot_root", "DotReaderMainModule"
118 | ]
119 | command = GenProjCommandLine()
120 | command.main(args)
121 |
122 | self.verify_genproj(app_path, 341, 676, 0)
123 | self.verify_lib(app_path, 'DotReaderLib17')
124 |
125 | @integration_test
126 | def test_dot_genproj_with_loc_mappings(self):
127 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
128 | print(app_path)
129 |
130 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
131 | loc_test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'loc_mappings.json')
132 | args = [
133 | "--output_directory", app_path, "--project_generator_type", "cocoapods", "--gen_type", "dot",
134 | "--dot_file", test_fixture_path, "--dot_root", "DotReaderMainModule",
135 | "--loc_json_file_path", loc_test_fixture_path
136 | ]
137 | command = GenProjCommandLine()
138 | command.main(args)
139 |
140 | self.verify_genproj(app_path, 342, 485, 2)
141 | self.verify_lib(app_path, 'DotReaderLib17')
142 |
143 | @integration_test
144 | def test_flat_multisuite(self):
145 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
146 | app_path = join(root_path, 'apps', 'mockapp')
147 | log_path = join(root_path, 'logs')
148 | args = [
149 | "--log_dir", log_path, "--app_gen_output_dir", root_path, "--test_build_only", "--skip_xcode_build",
150 | "--project_generator_type", "cocoapods"
151 | ]
152 | command = CommandLineMultisuite()
153 | command.main(args)
154 | self.assertGreater(os.listdir(app_path), 0)
155 | self.verify_genproj(app_path, 103, 602, 0)
156 | self.verify_lib(app_path, 'MockLib53')
157 |
158 | @integration_test
159 | def test_flat_multisuite_mocking_calls(self):
160 | test_cloc_out_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'cloc_out.json')
161 | cloc_out = read_file(test_cloc_out_path)
162 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
163 | app_path = join(root_path, 'apps', 'mockapp')
164 | log_path = join(root_path, 'logs')
165 | args = [
166 | "--log_dir", log_path, "--app_gen_output_dir", root_path, "--test_build_only", "--switch_xcode_versions",
167 | "--full_clean", "--project_generator_type", "cocoapods"
168 | ]
169 |
170 | # we need the unused named variable for mocking purposes
171 | # noinspection PyUnusedLocal
172 | def command_callable(command, stdin):
173 | if 'cloc' in command:
174 | return PopenBehaviour(stdout=cloc_out)
175 | elif 'xcodebuild -version' in command:
176 | return PopenBehaviour(stdout=b'Xcode 10.0\nBuild version 10A255\n')
177 | return PopenBehaviour(stdout=b'test_out', stderr=b'test_error')
178 |
179 | with testfixtures.Replacer() as rep:
180 | mock_popen = MockPopen()
181 | rep.replace('subprocess.Popen', mock_popen)
182 | mock_popen.set_default(behaviour=command_callable)
183 |
184 | with mock.patch('distutils.spawn.find_executable') as mock_find:
185 | mock_find.return_value = '/bin/ls' # A non empty return value basically means "I found that executable"
186 | CommandLineMultisuite().main(args)
187 | self.assertGreater(os.listdir(app_path), 0)
188 | self.verify_genproj(app_path, 103, 602, 0)
189 | self.verify_lib(app_path, 'MockLib53')
190 |
191 | @integration_test
192 | def test_dot_multisuite(self):
193 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
194 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
195 | app_path = join(root_path, 'apps', 'mockapp')
196 | log_path = join(root_path, 'logs')
197 | args = [
198 | "--log_dir", log_path, "--app_gen_output_dir", root_path, "--dot_file", test_fixture_path, "--dot_root",
199 | "DotReaderMainModule", "--skip_xcode_build", "--test_build_only", "--project_generator_type", "cocoapods"
200 | ]
201 | command = CommandLineMultisuite()
202 | command.main(args)
203 | self.assertGreater(os.listdir(app_path), 0)
204 | self.verify_genproj(app_path, 340, 339, 0)
205 | self.verify_lib(app_path, 'DotReaderLib17')
206 |
207 | @integration_test
208 | def test_all_multisuite(self):
209 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
210 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
211 | app_path = join(root_path, 'apps', 'mockapp')
212 | log_path = join(root_path, 'logs')
213 | args = [
214 | "--log_dir", log_path, "--app_gen_output_dir", root_path, "--dot_file", test_fixture_path, "--dot_root",
215 | "DotReaderMainModule", "--skip_xcode_build", "--swift_lines_of_code", "150000",
216 | "--project_generator_type", "cocoapods"
217 | ]
218 | command = CommandLineMultisuite()
219 | command.main(args)
220 | self.assertGreater(os.listdir(app_path), 0)
221 |
222 | # Note we are assuming that the last project to be generated is the dot project.
223 | # If you change the order of project generation, make this match whatever is the new 'last project'
224 | # It's a bit fragile, but it's better than not verifying anything currently
225 | self.verify_genproj(app_path, 340, 676, 0)
226 | self.verify_lib(app_path, 'DotReaderLib17')
227 |
--------------------------------------------------------------------------------
/tests/test_bazel_integration.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import os
18 | import tempfile
19 | import unittest
20 | from os.path import join
21 |
22 | import mock
23 | import testfixtures.popen
24 | from testfixtures.popen import MockPopen, PopenBehaviour
25 |
26 | from uberpoet.filegen import Language
27 | from uberpoet.genproj import GenProjCommandLine
28 | from uberpoet.multisuite import CommandLineMultisuite
29 |
30 | from .utils import integration_test, read_file
31 |
32 |
33 | class TestBazelIntegration(unittest.TestCase):
34 |
35 | def verify_genproj(self, app_path, dir_file_count, swift_file_count, objc_file_count):
36 | main_path = join(app_path, 'App')
37 |
38 | # Top level dir
39 | contents = os.listdir(app_path)
40 | self.assertGreater(len(contents), 0)
41 | self.assertEqual(len(contents), dir_file_count)
42 |
43 | swift_file_contents = []
44 | objc_file_contents = []
45 |
46 | for dirpath, dirnames, filenames in os.walk(app_path):
47 | for f in filenames:
48 | ext = os.path.splitext(f)[1]
49 | if ext == '.swift':
50 | swift_file_contents.append(os.path.join(dirpath, f))
51 | elif ext == '.h' or ext == '.m':
52 | objc_file_contents.append(os.path.join(dirpath, f))
53 |
54 | self.assertEqual(len(swift_file_contents), swift_file_count)
55 | self.assertEqual(len(objc_file_contents), objc_file_count)
56 |
57 | # App dir
58 | self.assertIn('App', contents)
59 | app_contents = os.listdir(main_path)
60 | self.assertGreater(len(app_contents), 0)
61 | for file_name in ['BUILD', 'AppDelegate.swift', 'Info.plist']:
62 | self.assertIn(file_name, app_contents)
63 | with open(join(main_path, file_name), 'r') as f:
64 | self.assertGreater(len(f.read()), 0)
65 | # TODO actually verify generated code?
66 |
67 | def verify_lib(self, app_path, lib_name, language=Language.SWIFT):
68 | lib_path = join(app_path, lib_name, 'Sources')
69 |
70 | lib_contents = os.listdir(lib_path)
71 | self.assertGreater(len(lib_contents), 0)
72 |
73 | if language == Language.SWIFT:
74 | files = list(['File0.swift'])
75 | elif language == Language.OBJC:
76 | files = list(['File0.h', 'File0.m'])
77 |
78 | for f in files:
79 | self.assertIn(f, lib_contents)
80 | with open(join(lib_path, f), 'r') as f:
81 | self.assertGreater(len(f.read()), 0)
82 | # TODO actually verify generated code?
83 |
84 | @integration_test
85 | def test_flat_genproj(self):
86 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
87 | args = [
88 | "--output_directory", app_path, "--blaze_module_path", "/apps/mockapp", "--gen_type", "flat",
89 | "--project_generator_type", "bazel",
90 | "--swift_lines_of_code", "150000"
91 | ]
92 | command = GenProjCommandLine()
93 | command.main(args)
94 |
95 | self.verify_genproj(app_path, 105, 901, 0)
96 | self.verify_lib(app_path, 'MockLib53')
97 |
98 | @integration_test
99 | def test_genproj_with_objc(self):
100 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
101 | args = [
102 | "--output_directory", app_path, "--blaze_module_path", "/apps/mockapp", "--gen_type", "flat",
103 | "--project_generator_type", "bazel",
104 | "--swift_lines_of_code", "0",
105 | "--objc_lines_of_code", "150000"
106 | ]
107 | command = GenProjCommandLine()
108 | command.main(args)
109 |
110 | # 1 Swift file count expected due to main.swift for the app target.
111 | self.verify_genproj(app_path, 105, 1, 2200)
112 | self.verify_lib(app_path, 'MockLib53', Language.OBJC)
113 |
114 | @integration_test
115 | def test_dot_genproj(self):
116 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
117 |
118 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
119 | args = [
120 | "--output_directory", app_path, "--blaze_module_path", "/apps/mockapp", "--gen_type", "dot",
121 | "--project_generator_type", "bazel",
122 | "--swift_lines_of_code", "150000", "--dot_file", test_fixture_path, "--dot_root", "DotReaderMainModule"
123 | ]
124 | command = GenProjCommandLine()
125 | command.main(args)
126 |
127 | self.verify_genproj(app_path, 342, 675, 0)
128 | self.verify_lib(app_path, 'DotReaderLib17')
129 |
130 | @integration_test
131 | def test_dot_genproj_with_loc_mappings(self):
132 | app_path = join(tempfile.gettempdir(), 'apps', 'mockapp')
133 |
134 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
135 | loc_test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'loc_mappings.json')
136 | args = [
137 | "--output_directory", app_path, "--blaze_module_path", "/apps/mockapp", "--gen_type", "dot",
138 | "--project_generator_type", "bazel",
139 | "--dot_file", test_fixture_path, "--dot_root", "DotReaderMainModule",
140 | "--loc_json_file_path", loc_test_fixture_path
141 | ]
142 | command = GenProjCommandLine()
143 | command.main(args)
144 |
145 | self.verify_genproj(app_path, 343, 484, 2)
146 | self.verify_lib(app_path, 'DotReaderLib17')
147 |
148 | @integration_test
149 | def test_flat_multisuite(self):
150 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
151 | app_path = join(root_path, 'apps', 'mockapp')
152 | log_path = join(root_path, 'logs')
153 | args = ["--log_dir", log_path, "--app_gen_output_dir", root_path, "--test_build_only", "--skip_xcode_build",
154 | "--project_generator_type", "bazel"]
155 | command = CommandLineMultisuite()
156 | command.main(args)
157 | self.assertGreater(os.listdir(app_path), 0)
158 | self.verify_genproj(app_path, 104, 601, 0)
159 | self.verify_lib(app_path, 'MockLib53')
160 |
161 | @integration_test
162 | def test_flat_multisuite_mocking_calls(self):
163 | test_cloc_out_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'cloc_out.json')
164 | cloc_out = read_file(test_cloc_out_path)
165 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
166 | app_path = join(root_path, 'apps', 'mockapp')
167 | log_path = join(root_path, 'logs')
168 | args = [
169 | "--log_dir", log_path, "--app_gen_output_dir", root_path, "--test_build_only", "--switch_xcode_versions",
170 | "--project_generator_type", "bazel",
171 | "--full_clean"
172 | ]
173 |
174 | # we need the unused named variable for mocking purposes
175 | # noinspection PyUnusedLocal
176 | def command_callable(command, stdin):
177 | if 'cloc' in command:
178 | return PopenBehaviour(stdout=cloc_out)
179 | elif 'xcodebuild -version' in command:
180 | return PopenBehaviour(stdout=b'Xcode 10.0\nBuild version 10A255\n')
181 | return PopenBehaviour(stdout=b'test_out', stderr=b'test_error')
182 |
183 | with testfixtures.Replacer() as rep:
184 | mock_popen = MockPopen()
185 | rep.replace('subprocess.Popen', mock_popen)
186 | mock_popen.set_default(behaviour=command_callable)
187 |
188 | with mock.patch('distutils.spawn.find_executable') as mock_find:
189 | mock_find.return_value = '/bin/ls' # A non empty return value basically means "I found that executable"
190 | CommandLineMultisuite().main(args)
191 | self.assertGreater(os.listdir(app_path), 0)
192 | self.verify_genproj(app_path, 104, 601, 0)
193 | self.verify_lib(app_path, 'MockLib53')
194 |
195 | @integration_test
196 | def test_dot_multisuite(self):
197 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
198 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
199 | app_path = join(root_path, 'apps', 'mockapp')
200 | log_path = join(root_path, 'logs')
201 | args = [
202 | "--log_dir", log_path, "--app_gen_output_dir", root_path, "--dot_file", test_fixture_path, "--dot_root",
203 | "DotReaderMainModule", "--skip_xcode_build", "--test_build_only",
204 | "--project_generator_type", "bazel"
205 | ]
206 | command = CommandLineMultisuite()
207 | command.main(args)
208 | self.assertGreater(os.listdir(app_path), 0)
209 | self.verify_genproj(app_path, 341, 338, 0)
210 | self.verify_lib(app_path, 'DotReaderLib17')
211 |
212 | @integration_test
213 | def test_all_multisuite(self):
214 | test_fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'test_dot.gv')
215 | root_path = join(tempfile.gettempdir(), 'multisuite_test')
216 | app_path = join(root_path, 'apps', 'mockapp')
217 | log_path = join(root_path, 'logs')
218 | args = [
219 | "--log_dir", log_path, "--app_gen_output_dir", root_path, "--dot_file", test_fixture_path, "--dot_root",
220 | "DotReaderMainModule", "--skip_xcode_build", "--swift_lines_of_code", "150000",
221 | "--project_generator_type", "bazel"
222 | ]
223 | command = CommandLineMultisuite()
224 | command.main(args)
225 | self.assertGreater(os.listdir(app_path), 0)
226 |
227 | # Note we are assuming that the last project to be generated is the dot project.
228 | # If you change the order of project generation, make this match whatever is the new 'last project'
229 | # It's a bit fragile, but it's better than not verifying anything currently
230 | self.verify_genproj(app_path, 341, 675, 0)
231 | self.verify_lib(app_path, 'DotReaderLib17')
232 |
--------------------------------------------------------------------------------
/uberpoet/blazeprojectgen.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import json
18 | import math
19 | import shutil
20 | from os.path import basename, dirname, join
21 |
22 | from . import locreader
23 | from .filegen import Language, ObjCHeaderFileGenerator, ObjCSourceFileGenerator, SwiftFileGenerator
24 | from .loccalc import LOCCalculator
25 | from .moduletree import ModuleNode
26 | from .util import first_in_dict, first_key, makedir
27 |
28 |
29 | class BlazeProjectGenerator(object):
30 | DIR_NAME = dirname(__file__)
31 | RESOURCE_DIR = join(DIR_NAME, "resources")
32 |
33 | def __init__(self, app_root, blaze_app_root, use_wmo=False, flavor='buck'):
34 | self.app_root = app_root
35 | self.blaze_app_root = blaze_app_root
36 | self.bzl_lib_template = self.load_resource("mock{}libtemplate.bzl".format(flavor))
37 | self.bzl_app_template = self.load_resource("mock{}apptemplate.bzl".format(flavor))
38 | self.app_delegate_template = self.load_resource("mockappdelegate")
39 | self.swift_gen = SwiftFileGenerator()
40 | self.objc_source_gen = ObjCSourceFileGenerator()
41 | self.objc_header_gen = ObjCHeaderFileGenerator()
42 | self.loc_calc = LOCCalculator()
43 | self.use_wmo = use_wmo
44 | self.flavor = flavor
45 | self.swift_file_size_loc = self.loc_calc.calculate_loc(
46 | self.swift_gen.gen_file(3, 3).text, self.swift_gen.language())
47 | self.objc_file_size_loc = self.loc_calc.calculate_loc(
48 | self.objc_source_gen.gen_file(3, 3).text, self.objc_source_gen.language())
49 |
50 | @property
51 | def wmo_state(self):
52 | return "YES" if self.use_wmo else "NO"
53 |
54 | @staticmethod
55 | def load_resource(name):
56 | with open(join(BlazeProjectGenerator.RESOURCE_DIR, name), "r") as f:
57 | return f.read()
58 |
59 | @staticmethod
60 | def copy_resource(name, dest):
61 | origin = join(BlazeProjectGenerator.RESOURCE_DIR, name)
62 | shutil.copyfile(origin, dest)
63 |
64 | @staticmethod
65 | def copy_resource_dir(name, dest):
66 | origin = join(BlazeProjectGenerator.RESOURCE_DIR, name)
67 | shutil.copytree(origin, dest)
68 |
69 | @staticmethod
70 | def write_file(path, text):
71 | with open(path, "w") as f:
72 | f.write(text)
73 |
74 | @staticmethod
75 | def make_list_str(items):
76 | return (",\n" + (" " * 8)).join(items)
77 |
78 | def make_dep_list(self, items):
79 | return self.make_list_str(["'//{0}:{0}'".format(i) for i in items])
80 |
81 | def make_scheme_list(self, items):
82 | return self.make_list_str(
83 | ["{2: <20} :'/{0}/{1}:{1}Scheme'".format(self.blaze_app_root, i, "'{}'".format(i)) for i in items])
84 |
85 | def example_command(self):
86 | if self.flavor == 'buck':
87 | return "buck project //App:App"
88 | elif self.flavor == 'bazel':
89 | return "Use Tulsi or XCHammer to generate an Xcode project."
90 |
91 | # Generation Functions
92 |
93 | def gen_app(self, app_node, node_list, target_swift_loc, target_objc_loc, loc_json_file_path):
94 | library_node_list = [n for n in node_list if n.node_type == ModuleNode.LIBRARY]
95 |
96 | if loc_json_file_path:
97 | loc_reader = locreader.LocFileReader()
98 | loc_reader.read_loc_file(loc_json_file_path)
99 | module_index = {}
100 | for n in library_node_list:
101 | loc = loc_reader.loc_for_module(n.name)
102 | language = loc_reader.language_for_module(n.name)
103 | module_index[n.name] = {
104 | "files": self.gen_lib_module(module_index, n, loc, language),
105 | "loc": loc,
106 | "language": language
107 | }
108 | else:
109 | total_code_units = 0
110 | for l in library_node_list:
111 | total_code_units += l.code_units
112 |
113 | total_loc = target_swift_loc + target_objc_loc
114 | swift_module_count_percentage = round(float(target_swift_loc) / total_loc, 2)
115 | loc_per_unit = total_loc / total_code_units
116 |
117 | module_index = {}
118 | max_swift_index = int(math.ceil((len(library_node_list) * swift_module_count_percentage)))
119 | for idx, n in enumerate(library_node_list):
120 | language = Language.OBJC if idx >= max_swift_index else Language.SWIFT
121 | module_index[n.name] = {
122 | "files": self.gen_lib_module(module_index, n, loc_per_unit, language),
123 | "loc": loc_per_unit,
124 | "language": language
125 | }
126 |
127 | app_module_dir = join(self.app_root, "App")
128 | makedir(app_module_dir)
129 |
130 | app_build_file = "BUCK" if self.flavor == 'buck' else "BUILD"
131 | app_files = {
132 | "AppDelegate.swift": self.gen_app_main(app_node, module_index),
133 | app_build_file: self.gen_app_build(app_node, library_node_list),
134 | }
135 |
136 | self.copy_resource("Info.plist", join(app_module_dir, "Info.plist"))
137 |
138 | if self.flavor == 'buck':
139 | self.copy_resource("mockbuckconfig", join(self.app_root, ".buckconfig"))
140 | elif self.flavor == 'bazel':
141 | self.copy_resource("mockbazelworkspace", join(self.app_root, "WORKSPACE"))
142 | self.copy_resource_dir("tools", join(self.app_root, "tools"))
143 |
144 | for name, text in app_files.iteritems():
145 | self.write_file(join(app_module_dir, name), text)
146 |
147 | if loc_json_file_path:
148 | # Copy the LOC file into the generated project.
149 | shutil.copyfile(loc_json_file_path, join(self.app_root, basename(loc_json_file_path)))
150 |
151 | serializable_module_index = {
152 | key: {
153 | "file_count": len(value["files"]),
154 | "loc": value["loc"]
155 | } for key, value in module_index.items()
156 | }
157 |
158 | with open(join(self.app_root, "module_index.json"), "w") as module_index_json_file:
159 | json.dump(serializable_module_index, module_index_json_file)
160 |
161 | def gen_app_build(self, node, all_nodes):
162 | module_dep_list = self.make_dep_list([i.name for i in node.deps])
163 | module_scheme_list = self.make_scheme_list([i.name for i in all_nodes])
164 | return self.bzl_app_template.format(module_scheme_list, module_dep_list, self.wmo_state)
165 |
166 | def gen_app_main(self, app_node, module_index):
167 | importing_module_name = app_node.deps[0].name
168 | file_index = first_in_dict(module_index[importing_module_name]["files"])
169 | language = module_index[importing_module_name]["language"]
170 | class_key = first_key(file_index.classes)
171 | class_index = first_in_dict(file_index.classes)
172 | function_key = first_in_dict(class_index)[0]
173 | return self.swift_gen.gen_main(self.app_delegate_template, importing_module_name, class_key, function_key,
174 | language)
175 |
176 | # Library Generation
177 |
178 | def gen_lib_module(self, module_index, module_node, loc_per_unit, language):
179 | deps = self.make_dep_list([i.name for i in module_node.deps])
180 | build_text = self.bzl_lib_template.format(module_node.name, deps, self.wmo_state)
181 | # We now return a topologically sorted list of the graph which means that we will already have the
182 | # deps of a module inside the module index before we process this one. This allows us to reach into
183 | # the generated sources for the dependencies in order to create an instance of their class and
184 | # invoke their functions.
185 | deps_from_index = [{n.name: module_index[n.name]} for n in module_node.deps]
186 |
187 | # Make Text
188 | if language == Language.SWIFT:
189 | file_count = (
190 | max(self.swift_file_size_loc, loc_per_unit) * module_node.code_units) / self.swift_file_size_loc
191 | if file_count < 1:
192 | raise ValueError(
193 | "Lines of code count is too small for the module {} to fit one file, increase it.".format(
194 | module_node.name))
195 | files = {
196 | "File{}.swift".format(i): self.swift_gen.gen_file(3, 3, deps_from_index) for i in xrange(file_count)
197 | }
198 | elif language == Language.OBJC:
199 | file_count = (max(self.objc_file_size_loc, loc_per_unit) * module_node.code_units) / self.objc_file_size_loc
200 | if file_count < 1:
201 | raise ValueError(
202 | "Lines of code count is too small for the module {} to fit one file, increase it.".format(
203 | module_node.name))
204 | files = {}
205 | for i in xrange(file_count):
206 | objc_source_file = self.objc_source_gen.gen_file(
207 | 3, 3, import_list=deps_from_index + ['File{}.h'.format(i)])
208 | files["File{}.m".format(i)] = objc_source_file
209 | files["File{}.h".format(i)] = self.objc_header_gen.gen_file(objc_source_file)
210 |
211 | # Make Module Directories
212 | module_dir_path = join(self.app_root, module_node.name)
213 | files_dir_path = join(module_dir_path, "Sources")
214 | makedir(module_dir_path)
215 | makedir(files_dir_path)
216 |
217 | # Write BUCK or BUILD Files
218 | build_name = "BUCK" if self.flavor == 'buck' else "BUILD"
219 | build_path = join(module_dir_path, build_name)
220 | self.write_file(build_path, build_text)
221 |
222 | # Write Swift Files
223 | for file_name, file_obj in files.iteritems():
224 | file_path = join(files_dir_path, file_name)
225 | self.write_file(file_path, file_obj.text)
226 | file_obj.text = "" # Save memory after write
227 |
228 | module_node.extra_info = files
229 |
230 | return files
231 |
--------------------------------------------------------------------------------
/uberpoet/cpprojectgen.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | import json
18 | import math
19 | import shutil
20 | from os.path import basename, dirname, join
21 |
22 | from . import locreader
23 | from .filegen import Language, ObjCHeaderFileGenerator, ObjCSourceFileGenerator, SwiftFileGenerator
24 | from .loccalc import LOCCalculator
25 | from .moduletree import ModuleNode
26 | from .util import first_in_dict, first_key, makedir
27 |
28 |
29 | class CocoaPodsProjectGenerator(object):
30 | DIR_NAME = dirname(__file__)
31 | RESOURCE_DIR = join(DIR_NAME, "resources")
32 |
33 | def __init__(self,
34 | app_root,
35 | use_wmo=False,
36 | use_dynamic_linking=False,
37 | use_deterministic_uuids=True,
38 | generate_multiple_pod_projects=False):
39 | self.app_root = app_root
40 | self.pod_lib_template = self.load_resource("mockcplibtemplate.podspec")
41 | self.pod_app_template = self.load_resource("mockcpapptemplate.podspec")
42 | self.podfile_template = self.load_resource("mockpodfile")
43 | self.app_delegate_template = self.load_resource("mockappdelegate")
44 | self.swift_gen = SwiftFileGenerator()
45 | self.objc_source_gen = ObjCSourceFileGenerator()
46 | self.objc_header_gen = ObjCHeaderFileGenerator()
47 | self.loc_calc = LOCCalculator()
48 | self.use_wmo = use_wmo
49 | self.use_dynamic_linking = use_dynamic_linking
50 | self.use_deterministic_uuids = use_deterministic_uuids
51 | self.generate_multiple_pod_projects = generate_multiple_pod_projects
52 | self.swift_file_size_loc = self.loc_calc.calculate_loc(
53 | self.swift_gen.gen_file(3, 3).text, self.swift_gen.language())
54 | self.objc_file_size_loc = self.loc_calc.calculate_loc(
55 | self.objc_source_gen.gen_file(3, 3).text, self.objc_source_gen.language())
56 |
57 | @property
58 | def wmo_state(self):
59 | return "YES" if self.use_wmo else "NO"
60 |
61 | @staticmethod
62 | def load_resource(name):
63 | with open(join(CocoaPodsProjectGenerator.RESOURCE_DIR, name), "r") as f:
64 | return f.read()
65 |
66 | @staticmethod
67 | def copy_resource(name, dest):
68 | origin = join(CocoaPodsProjectGenerator.RESOURCE_DIR, name)
69 | shutil.copyfile(origin, dest)
70 |
71 | @staticmethod
72 | def write_file(path, text):
73 | with open(path, "w") as f:
74 | f.write(text)
75 |
76 | @staticmethod
77 | def make_list_str(items, padding=8):
78 | return ("\n" + (" " * padding)).join(items)
79 |
80 | def make_dep_list(self, items):
81 | return self.make_list_str(["s.dependency '{0}'".format(i) for i in items])
82 |
83 | def make_podfile_dep_list(self, items):
84 | return self.make_list_str(["pod '{0}', :path => '{0}/{0}.podspec'".format(i) for i in items], 4)
85 |
86 | def example_command(self):
87 | return "pod install"
88 |
89 | # Generation Functions
90 |
91 | def gen_app(self, app_node, node_list, target_swift_loc, target_objc_loc, loc_json_file_path):
92 | library_node_list = [n for n in node_list if n.node_type == ModuleNode.LIBRARY]
93 |
94 | if loc_json_file_path:
95 | loc_reader = locreader.LocFileReader()
96 | loc_reader.read_loc_file(loc_json_file_path)
97 | module_index = {}
98 | for n in library_node_list:
99 | loc = loc_reader.loc_for_module(n.name)
100 | language = loc_reader.language_for_module(n.name)
101 | module_index[n.name] = {
102 | "files": self.gen_lib_module(module_index, n, loc, language),
103 | "loc": loc,
104 | "language": language
105 | }
106 | else:
107 | total_code_units = 0
108 | for l in library_node_list:
109 | total_code_units += l.code_units
110 |
111 | total_loc = target_swift_loc + target_objc_loc
112 | swift_module_count_percentage = round(float(target_swift_loc) / total_loc, 2)
113 | loc_per_unit = total_loc / total_code_units
114 |
115 | module_index = {}
116 | max_swift_index = int(math.ceil((len(library_node_list) * swift_module_count_percentage)))
117 | for idx, n in enumerate(library_node_list):
118 | language = Language.OBJC if idx >= max_swift_index else Language.SWIFT
119 | module_index[n.name] = {
120 | "files": self.gen_lib_module(module_index, n, loc_per_unit, language),
121 | "loc": loc_per_unit,
122 | "language": language
123 | }
124 |
125 | app_module_dir = join(self.app_root, "App")
126 | makedir(app_module_dir)
127 |
128 | app_files = {
129 | "AppDelegate.swift": self.gen_app_main(app_node, module_index),
130 | "dummy.swift": "",
131 | "AppContainer.podspec": self.gen_app_podspec(app_node),
132 | }
133 |
134 | self.copy_resource("Info.plist", join(app_module_dir, "Info.plist"))
135 |
136 | for name, text in app_files.iteritems():
137 | self.write_file(join(app_module_dir, name), text)
138 |
139 | if loc_json_file_path:
140 | # Copy the LOC file into the generated project.
141 | shutil.copyfile(loc_json_file_path, join(self.app_root, basename(loc_json_file_path)))
142 |
143 | podfile_text = self.gen_podfile(library_node_list)
144 | podfile_path = join(self.app_root, "Podfile")
145 | self.write_file(podfile_path, podfile_text)
146 |
147 | serializable_module_index = {
148 | key: {
149 | "file_count": len(value["files"]),
150 | "loc": value["loc"]
151 | } for key, value in module_index.items()
152 | }
153 |
154 | with open(join(self.app_root, "module_index.json"), "w") as module_index_json_file:
155 | json.dump(serializable_module_index, module_index_json_file)
156 |
157 | def gen_app_podspec(self, node):
158 | module_dep_list = self.make_dep_list([i.name for i in node.deps])
159 | return self.pod_app_template.format(module_dep_list, self.wmo_state)
160 |
161 | def gen_app_main(self, app_node, module_index):
162 | importing_module_name = app_node.deps[0].name
163 | file_index = first_in_dict(module_index[importing_module_name]["files"])
164 | language = module_index[importing_module_name]["language"]
165 | class_key = first_key(file_index.classes)
166 | class_index = first_in_dict(file_index.classes)
167 | function_key = first_in_dict(class_index)[0]
168 | return self.swift_gen.gen_main(self.app_delegate_template, importing_module_name, class_key, function_key,
169 | language)
170 |
171 | # Library Generation
172 |
173 | def gen_lib_module(self, module_index, module_node, loc_per_unit, language):
174 | # Make Podspec Text
175 | deps = self.make_dep_list([i.name for i in module_node.deps])
176 | pod_text = self.pod_lib_template.format(module_node.name, deps, self.wmo_state)
177 | # We now return a topologically sorted list of the graph which means that we will already have the
178 | # deps of a module inside the module index before we process this one. This allows us to reach into
179 | # the generated sources for the dependencies in order to create an instance of their class and
180 | # invoke their functions.
181 | deps_from_index = [{n.name: module_index[n.name]} for n in module_node.deps]
182 |
183 | # Make Text
184 | if language == Language.SWIFT:
185 | file_count = (
186 | max(self.swift_file_size_loc, loc_per_unit) * module_node.code_units) / self.swift_file_size_loc
187 | if file_count < 1:
188 | raise ValueError(
189 | "Lines of code count is too small for the module {} to fit one file, increase it.".format(
190 | module_node.name))
191 | files = {
192 | "File{}.swift".format(i): self.swift_gen.gen_file(3, 3, deps_from_index) for i in xrange(file_count)
193 | }
194 | elif language == Language.OBJC:
195 | file_count = (max(self.objc_file_size_loc, loc_per_unit) * module_node.code_units) / self.objc_file_size_loc
196 | if file_count < 1:
197 | raise ValueError(
198 | "Lines of code count is too small for the module {} to fit one file, increase it.".format(
199 | module_node.name))
200 | files = {}
201 | for i in xrange(file_count):
202 | objc_source_file = self.objc_source_gen.gen_file(
203 | 3, 3, import_list=deps_from_index + ['File{}.h'.format(i)])
204 | files["File{}.m".format(i)] = objc_source_file
205 | files["File{}.h".format(i)] = self.objc_header_gen.gen_file(objc_source_file)
206 |
207 | # Make Module Directories
208 | module_dir_path = join(self.app_root, module_node.name)
209 | files_dir_path = join(module_dir_path, "Sources")
210 | makedir(module_dir_path)
211 | makedir(files_dir_path)
212 |
213 | # Write podspec File
214 | pod_path = join(module_dir_path, "{0}.podspec".format(module_node.name))
215 | self.write_file(pod_path, pod_text)
216 |
217 | # Write Swift Files
218 | for file_name, file_obj in files.iteritems():
219 | file_path = join(files_dir_path, file_name)
220 | self.write_file(file_path, file_obj.text)
221 | file_obj.text = "" # Save memory after write
222 |
223 | module_node.extra_info = files
224 |
225 | return files
226 |
227 | # Podfile Generation
228 |
229 | def gen_podfile(self, all_nodes):
230 | podfile_module_dep_list = self.make_podfile_dep_list([i.name for i in all_nodes])
231 | link_style = ":dynamic" if self.use_dynamic_linking else ":static"
232 | use_deterministic_uuids = str(self.use_deterministic_uuids).lower()
233 | generate_multiple_pod_projects = str(self.generate_multiple_pod_projects).lower()
234 |
235 | return self.podfile_template.format(
236 | "pod 'AppContainer', :path => 'App/AppContainer.podspec', :appspecs => ['App']", podfile_module_dep_list,
237 | link_style, use_deterministic_uuids, generate_multiple_pod_projects)
238 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2018 Uber Technologies Inc.
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/uberpoet/filegen.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2018 Uber Technologies, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from __future__ import absolute_import
16 |
17 | from .util import first_in_dict, seed
18 |
19 | uber_poet_header = """
20 | // This code was @generated by Uber Poet, a mock application generator.
21 | // Check it out at https://github.com/uber/uber-poet
22 | """
23 |
24 | # This method can only be invoked between Swift modules as it is not
25 | # ObjC friendly due to the use of generics.
26 | swift_func_template = """
27 | public func complexCrap{0}(arg: Int, stuff:T) -> Int {{
28 | let a = Int(4 * {1} + Int(Float(arg) / 32.0))
29 | let b = Int(4 * {1} + Int(Float(arg) / 32.0))
30 | let c = Int(4 * {1} + Int(Float(arg) / 32.0))
31 | return Int(4 * {1} + Int(Float(arg) / 32.0)) + a + b + c
32 | }}"""
33 |
34 | swift_func_objc_friendly_template = """
35 | @objc
36 | public func complexStuff{0}(arg: String) -> String {{
37 | let randomString = NSUUID().uuidString
38 | return ("\\(arg)-\\(randomString)")
39 | }}
40 | """
41 |
42 | swift_class_template = """
43 | @objc
44 | public class MyClass{0}: NSObject {{
45 | public let x: Int
46 | public let y: String
47 |
48 | @objc
49 | public override init() {{
50 | x = 7
51 | y = "hi"
52 | {2}
53 | }}
54 |
55 | {1}
56 | }}"""
57 |
58 | swift_to_swift_func_call_template = """MyClass{0}().complexCrap{1}(arg: 4, stuff: 2)"""
59 | swift_to_objc_func_call_template = """MyClass_{0}().complexCrap{1}(4, stuff: \"2\")"""
60 |
61 | swift_to_swift_objc_friendly_func_call_template = """MyClass{0}().complexStuff{1}(arg: \"4\")"""
62 | swift_to_objc_friendly_func_call_template = """MyClass_{0}().complexStuff{1}(arg: \"4\")"""
63 |
64 | objc_to_swift_func_call_template = """[[[MyClass{} alloc] init] complexStuff{}WithArg:@\"4\"];"""
65 | objc_to_objc_func_call_template = """[[[MyClass_{} alloc] init] complexCrap{}:4 stuff:@\"2\"];"""
66 |
67 | objc_header_func_template = """- (int)complexCrap{0}:(int)arg stuff:(nonnull NSString *)stuff;"""
68 |
69 | objc_source_func_template = """
70 | - (int)complexCrap{0}:(int)arg stuff:(nonnull NSString *)stuff;
71 | {{
72 | int a = (int)(4 * self.{1} + (int)((float)arg / 32.0));
73 | int b = (int)(4 * self.{1} + (int)((float)arg / 32.0));
74 | int c = (int)(4 * self.{1} + (int)((float)arg / 32.0));
75 | return (int)(4 * self.{1} + (int)((float)arg / 32.0)) + a + b + c;
76 | }}"""
77 |
78 | objc_system_import_template = """
79 | @import Foundation;
80 | """
81 |
82 | objc_header_template = """
83 | @interface MyClass_{0} : NSObject
84 |
85 | @property(nonatomic, readonly) int x;
86 | @property(nonatomic, readonly, nonnull) NSString *y;
87 |
88 | - (nonnull instancetype)initWithX:(int)x y:(nonnull NSString *)y;
89 | {1}
90 |
91 | @end
92 | """
93 |
94 | objc_source_template = """
95 | @implementation MyClass_{0}
96 |
97 | - (nonnull instancetype)initWithX:(int)x y:(nonnull NSString *)y;
98 | {{
99 | self = [super init];
100 | NSParameterAssert(self);
101 |
102 | _x = x;
103 | _y = y;
104 | {2}
105 | return self;
106 | }}
107 | {1}
108 |
109 | @end
110 | """
111 |
112 |
113 | def get_func_call_template(from_language, to_language, function_type):
114 | if function_type == FuncType.SWIFT_ONLY:
115 | if from_language == Language.OBJC:
116 | raise ValueError("Cannot invoke SWIFT_ONLY method from ObjC!")
117 | return swift_to_swift_func_call_template if to_language == Language.SWIFT else swift_to_objc_func_call_template
118 | elif function_type == FuncType.OBJC_FRIENDLY:
119 | if from_language == Language.SWIFT:
120 | if to_language == Language.SWIFT:
121 | return swift_to_swift_objc_friendly_func_call_template
122 | elif to_language == Language.OBJC:
123 | return swift_to_objc_friendly_func_call_template
124 | elif from_language == Language.OBJC:
125 | if to_language == Language.SWIFT:
126 | return objc_to_swift_func_call_template
127 | elif to_language == Language.OBJC:
128 | return objc_to_objc_func_call_template
129 |
130 |
131 | def get_import_func_calls(from_language, import_list, indent=0):
132 | out = []
133 | for i in import_list:
134 | if type(i) is str:
135 | continue
136 | module = first_in_dict(i)
137 | to_language = module["language"]
138 | for file_result in module["files"].values():
139 | for class_num, class_funcs in file_result.classes.items():
140 | for func_type, func_nums in class_funcs.items():
141 | for func_num in func_nums:
142 | if (func_type == FuncType.SWIFT_ONLY and from_language == Language.OBJC and
143 | to_language == Language.SWIFT):
144 | # We cannot invoke Swift only functions from ObjC since they use generics.
145 | continue
146 | text = get_func_call_template(from_language, to_language, func_type).format(class_num, func_num)
147 | indented_text = '\n'.join(" " * indent + line for line in text.splitlines())
148 | out.append(indented_text)
149 |
150 | return "\n".join(out)
151 |
152 |
153 | class Language(object):
154 | SWIFT = 'Swift'
155 | OBJC = 'Objective-C'
156 |
157 | @staticmethod
158 | def enum_list():
159 | return [Language.SWIFT, Language.OBJC]
160 |
161 |
162 | class FuncType(object):
163 | """
164 | Describes the type of function added to a class. Helps distinguish how to invoke a function
165 | between modules.
166 | """
167 | SWIFT_ONLY = 'swift_only'
168 | OBJC_FRIENDLY = 'objc_friendly'
169 |
170 |
171 | class FileResult(object):
172 |
173 | def __init__(self, text, functions, classes):
174 | super(FileResult, self).__init__()
175 | self.text = text # string
176 | self.text_line_count = len(text.split('\n'))
177 | self.functions = functions # list of indexes
178 | self.classes = classes # {class index: {func type: function indexes}}
179 |
180 | def __str__(self):
181 | return "".format(self.text_line_count, self.functions,
182 | self.classes)
183 |
184 |
185 | class FileGenerator(object):
186 |
187 | def gen_file(self, class_count, function_count):
188 | return FileResult("", [], {})
189 |
190 |
191 | class ObjCHeaderFileGenerator(FileGenerator):
192 |
193 | @staticmethod
194 | def language():
195 | return Language.OBJC
196 |
197 | @staticmethod
198 | def extension():
199 | return '.h'
200 |
201 | @staticmethod
202 | def gen_func(nums):
203 | out = []
204 |
205 | for num in nums:
206 | out.append(objc_header_func_template.format(num))
207 |
208 | return "\n".join(out), nums
209 |
210 | def get_header(self, objc_class):
211 | out = []
212 | class_nums = {}
213 |
214 | for c in objc_class.classes:
215 | num = c
216 | func_out, func_nums = self.gen_func(objc_class.classes[c][FuncType.OBJC_FRIENDLY])
217 | out.append(objc_header_template.format(num, func_out))
218 | class_nums[num] = {FuncType.OBJC_FRIENDLY: func_nums}
219 |
220 | return "\n".join(out), class_nums
221 |
222 | def gen_file(self, objc_class):
223 | class_out, class_nums = self.get_header(objc_class)
224 |
225 | chunks = [uber_poet_header, objc_system_import_template, class_out]
226 |
227 | return FileResult("\n".join(chunks), [], class_nums)
228 |
229 |
230 | class ObjCSourceFileGenerator(FileGenerator):
231 |
232 | @staticmethod
233 | def language():
234 | return Language.OBJC
235 |
236 | @staticmethod
237 | def extension():
238 | return '.m'
239 |
240 | @staticmethod
241 | def gen_func(function_count, var_name):
242 | out = []
243 | nums = []
244 |
245 | for _ in xrange(function_count):
246 | num = seed()
247 | text = objc_source_func_template.format(num, var_name)
248 | nums.append(num)
249 | out.append(text)
250 |
251 | return "\n".join(out), nums
252 |
253 | def gen_class(self, class_count, func_per_class_count, import_list):
254 | out = []
255 | class_nums = {}
256 |
257 | for _ in xrange(class_count):
258 | num = seed()
259 | func_out, func_nums = self.gen_func(func_per_class_count, "x")
260 | func_call_out = get_import_func_calls(self.language(), import_list, indent=4)
261 | out.append(objc_source_template.format(num, func_out, func_call_out))
262 | class_nums[num] = {FuncType.OBJC_FRIENDLY: func_nums}
263 |
264 | return "\n".join(out), class_nums
265 |
266 | def gen_file(self, class_count, function_count, import_list=None):
267 | if import_list is None:
268 | import_list = []
269 | imports = []
270 | for i in import_list:
271 | if type(i) is str:
272 | imports.append('#import \"{}\"'.format(i))
273 | elif type(i) is dict:
274 | imports.append('@import {};'.format(i.keys()[0]))
275 | imports_out = "\n".join(imports)
276 | class_out, class_nums = self.gen_class(class_count, 5, import_list)
277 |
278 | chunks = [uber_poet_header, objc_system_import_template, imports_out, class_out]
279 |
280 | return FileResult("\n".join(chunks), [], class_nums)
281 |
282 |
283 | class SwiftFileGenerator(FileGenerator):
284 |
285 | def __init__(self):
286 | self.gen_state = {}
287 |
288 | @staticmethod
289 | def language():
290 | return Language.SWIFT
291 |
292 | @staticmethod
293 | def extension():
294 | return '.swift'
295 |
296 | @staticmethod
297 | def gen_func(function_count, var_name, indent=0):
298 | out = []
299 | nums = []
300 |
301 | for _ in xrange(function_count):
302 | num = seed()
303 | text = swift_func_template.format(num, var_name)
304 | indented_text = '\n'.join(" " * indent + line for line in text.splitlines())
305 | nums.append(num)
306 | out.append(indented_text)
307 |
308 | return "\n".join(out), nums
309 |
310 | @staticmethod
311 | def gen_objc_friendly_func(indent=0):
312 | out = []
313 | nums = []
314 |
315 | num = seed()
316 | text = swift_func_objc_friendly_template.format(num)
317 | indented_text = '\n'.join(" " * indent + line for line in text.splitlines())
318 | nums.append(num)
319 | out.append(indented_text)
320 |
321 | return "\n".join(out), nums
322 |
323 | def gen_class(self, class_count, func_per_class_count, import_list):
324 | out = []
325 | class_nums = {}
326 |
327 | for _ in xrange(class_count):
328 | num = seed()
329 | swift_only_func_out, swift_only_func_nums = self.gen_func(func_per_class_count, "x", indent=4)
330 | swift_objc_friendly_func_out, swift_objc_friendly_func_nums = self.gen_objc_friendly_func(indent=4)
331 | func_out = swift_only_func_out + "\n" + swift_objc_friendly_func_out
332 | func_call_out = get_import_func_calls(self.language(), import_list, indent=8)
333 | out.append(swift_class_template.format(num, func_out, func_call_out))
334 |
335 | class_nums[num] = {
336 | FuncType.SWIFT_ONLY: swift_only_func_nums,
337 | FuncType.OBJC_FRIENDLY: swift_objc_friendly_func_nums
338 | }
339 |
340 | return "\n".join(out), class_nums
341 |
342 | def gen_file(self, class_count, function_count, import_list=None):
343 | if import_list is None:
344 | import_list = []
345 | imports_out = "\n".join(["import {}".format(i if type(i) is str else i.keys()[0]) for i in import_list])
346 | func_out, func_nums = self.gen_func(function_count, "7")
347 | class_out, class_nums = self.gen_class(class_count, 5, import_list)
348 |
349 | chunks = [uber_poet_header, imports_out, func_out, class_out]
350 |
351 | return FileResult("\n".join(chunks), func_nums, class_nums)
352 |
353 | @staticmethod
354 | def gen_main(template, importing_module_name, class_num, func_num, to_language):
355 | import_line = 'import {}'.format(importing_module_name)
356 | action_expr = get_func_call_template(Language.SWIFT, to_language, FuncType.SWIFT_ONLY).format(
357 | class_num, func_num)
358 | print_line = 'print("\\({})")'.format(action_expr)
359 | return template.format(uber_poet_header, import_line, print_line)
360 |
--------------------------------------------------------------------------------