Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update readme with meta packages #361

Merged
merged 7 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/auto-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ name: auto-committer

on:
schedule:
# run at 1900UTC (3am +8GMT) everyday, 2 hours after auto updater
- cron: "0 19 * * *"
# run at 1800UTC (2am +8GMT) and 2000UTC (4am +8GMT) everyday, 1 hour after both auto updaters
- cron: "0 18,20 * * *"
workflow_dispatch:


Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/auto-meta-updater.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: auto-updater

on:
schedule:
# run at 1900UTC (3am +8GMT) everyday, 2 hours after regular updater, 1 hour after first commiter
- cron: "0 19 * * *"
workflow_dispatch:


jobs:
update:

runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Set git username and email, so we can commit and push
run: |
git config --global user.email "[email protected]"
git config --global user.name "Isen Ng"

- name: Run auto_meta_updater.py
env:
GITHUB_USER: ${{ secrets.GITHUB_USER }}
GITHUB_TOKEN: ${{ secrets.UPDATER_PUSH_TOKEN }}
run: ./auto_meta_updater.py --really_commit --really_push
36 changes: 20 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,34 +101,38 @@ jobs:
require 'cask/installer'

cask = Cask::CaskLoader.load('${{ matrix.cask.path }}')

was_installed = cask.installed?
manual_installer = cask.artifacts.any? { |artifact|
artifact.is_a?(Cask::Artifact::Installer::ManualInstaller)
}

manual_installer = cask.artifacts.any? do |artifact|
if defined?(artifact.manual_install)
artifact.manual_install
end
end

macos_requirement_satisfied = if macos_requirement = cask.depends_on.macos
macos_requirement.satisfied?
else
true
end

cask_conflicts = cask.conflicts_with&.dig(:cask).to_a.select { |c| Cask::CaskLoader.load(c).installed? }
formula_conflicts = cask.conflicts_with&.dig(:formula).to_a.select { |f| Formula[f].any_version_installed? }

installer = Cask::Installer.new(cask)
cask_and_formula_dependencies = installer.missing_cask_and_formula_dependencies

cask_dependencies = cask_and_formula_dependencies.select { |d| d.is_a?(Cask::Cask) }.map(&:full_name)
formula_dependencies = cask_and_formula_dependencies.select { |d| d.is_a?(Formula) }.map(&:full_name)

puts "::set-output name=was_installed::#{JSON.generate(was_installed)}"
puts "::set-output name=manual_installer::#{JSON.generate(manual_installer)}"
puts "::set-output name=macos_requirement_satisfied::#{JSON.generate(macos_requirement_satisfied)}"
puts "::set-output name=cask_conflicts::#{JSON.generate(cask_conflicts)}"
puts "::set-output name=cask_dependencies::#{JSON.generate(cask_dependencies)}"
puts "::set-output name=formula_conflicts::#{JSON.generate(formula_conflicts)}"
puts "::set-output name=formula_dependencies::#{JSON.generate(formula_dependencies)}"

File.open(ENV.fetch("GITHUB_OUTPUT"), "a") do |f|
f.puts "was_installed=#{JSON.generate(was_installed)}"
f.puts "manual_installer=#{JSON.generate(manual_installer)}"
f.puts "macos_requirement_satisfied=#{JSON.generate(macos_requirement_satisfied)}"
f.puts "cask_conflicts=#{JSON.generate(cask_conflicts)}"
f.puts "cask_dependencies=#{JSON.generate(cask_dependencies)}"
f.puts "formula_conflicts=#{JSON.generate(formula_conflicts)}"
f.puts "formula_dependencies=#{JSON.generate(formula_dependencies)}"
end
EOF
if: always() && steps.gems.outcome == 'success' && matrix.cask

Expand Down
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ brew install --cask <version>
dotnet --list-sdks
```

### Meta versions

| Version | .NET SDK |
| ------------- | -------- |
| `dotnet-sdk8` | 8.0.401 |
| `dotnet-sdk7` | 7.0.410 |
| `dotnet-sdk6` | 6.0.425 |
| `dotnet-sdk5` | 5.0.408 |
| `dotnet-sdk3` | 3.1.426 |
| `dotnet-sdk2` | 2.2.402 |

**Note**: These meta-packages installs the latest MINOR and PATCH versions from the existing versions list below.

**Note 2**: See each corresponding version below for `arch` and `remarks`.

### Versions

Expand Down Expand Up @@ -60,13 +74,10 @@ after installing/upgrading to .NET SDK 5.

| Version | .NET SDK | Arch | Remarks |
| --------------------- | -------------------------- | ----------- | ------- |
| `dotnet-sdk9-preview` | 9.0.100-preview.7.24407.12 | x64 & arm64 | |
| `dotnet-sdk9-preview` | 9.0.100-preview.7.24407.12 | x64 & arm64 | |
| `dotnet-sdk8-preview` | 8.0.101-rc.2.23502.2 | x64 & arm64 | |
| `dotnet-sdk7-preview` | 7.0.100-rc.2.22477.23 | x64 & arm64 | |

**Note**: Preview versions is a newly supported feature (as of September 2022). Please send feedback/create issues
if there are problems with the compatibility of the preview versions.


## Uninstalling

Expand Down
2 changes: 1 addition & 1 deletion auto_committer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def process_ready_pull_requests(self, pull_requests):

for pull_request in pull_requests:
git_service.merge_pull_request(pull_request)
time.sleep(10)
time.sleep(20)


class GitService:
Expand Down
238 changes: 238 additions & 0 deletions auto_meta_updater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import os
import re
from typing import List, Dict
import subprocess


class MetaCask:
def __init__(self, meta_cask_name: str, depends_on_filename: str, major: str, minor: str, patch: str):
self.meta_cask_name = meta_cask_name
self.depends_on_filename = depends_on_filename
self.major = major
self.minor = minor
self.patch = patch

def is_newer_than(self, other):
if self.major > other.major:
return True
elif self.major < other.major:
return False

if self.minor > other.minor:
return True
elif self.minor < other.minor:
return False

if self.patch > other.patch:
return True
elif self.patch < other.patch:
return False

return False

def __str__(self):
return f'{self.major}.{self.minor}.{self.patch}'


class DependsOnCask:
def __init__(self, cask_name: str, sdk_version: str, runtime_version: str, depends_on_cask: str, depends_on_macos: str, is_arm_supported: bool):
self.cask_name = cask_name
self.sdk_version = sdk_version
self.runtime_version = runtime_version
self.depends_on_cask = depends_on_cask
self.depends_on_macos = depends_on_macos
self.is_arm_supported = is_arm_supported


class TemplateService:
__cask_template_path = "meta-cask.rb.template";
__cask_x64_template_path = "meta-cask.x64.rb.template";

def generate(self, meta_cask: MetaCask, depends_on_cask: DependsOnCask) -> str:
if depends_on_cask.is_arm_supported:
path = self.__cask_template_path
else:
path = self.__cask_x64_template_path

with open(path, 'r') as file:
content = file.read()

content = content.replace('{cask_name}', meta_cask.meta_cask_name)
content = content.replace('{sdk_version}', depends_on_cask.sdk_version)
content = content.replace('{runtime_version}', depends_on_cask.runtime_version)
content = content.replace('{depends_on_cask}', depends_on_cask.depends_on_cask)
content = content.replace('{depends_on_macos}', depends_on_cask.depends_on_macos)

return content


class MetaLookupService:
__cask_version_regex = re.compile(r"(?P<name>dotnet-sdk)(?P<major>\d+)-(?P<minor>\d+)-(?P<patch>\d+)")

def __init__(self, cask_directory):
self.cask_directory = cask_directory

def generate_version_lookup(self) -> Dict[str, MetaCask]:
result = {}
casks = self.__list_work_files();
for cask_filename in casks:
match = self.__cask_version_regex.search(cask_filename)
if match is None:
print("ignored {}: No version match match for filename".format(cask_filename))
continue

major = match.group("major")
minor = match.group("minor")
patch = match.group("patch")

meta_cask_name = "{}{}".format(match.group("name"), major)
current = MetaCask(meta_cask_name, cask_filename, major, minor, patch)

if meta_cask_name not in result:
result[meta_cask_name] = current
continue

existing = result[meta_cask_name]
if current.is_newer_than(existing):
result[meta_cask_name] = current

return list(result.values())

def __list_work_files(self):
result = []
for file in os.listdir(self.cask_directory):
if not os.path.isfile(os.path.join(self.cask_directory, file)):
continue

if "-preview" in file:
continue

if "-rc" in file:
continue

# major meta cask only contains 1 dash. eg `dotnet-sdk8`
if file.count('-') is 1:
continue

result.append(file)

return result


class DependsOnCaskParser:
__cask_name_pattern = re.compile(r'cask "(?P<cask_name>[a-z0-9-]+)" do')
__version_pattern = re.compile(r'version "(?P<sdk_version>\d+.\d+.\d+),(?P<runtime_version>\d+.\d+.\d+)"')
__macos_pattern = re.compile(r'depends_on macos: "(?P<depends_on_macos>[>= :a-z_]+)"')
__arm_pattern = re.compile(r'arch arm: "arm64", intel: "x64"')

def __init__(self, cask_directory):
self.cask_directory = cask_directory

def parse(self, meta_cask: MetaCask) -> DependsOnCask:
path = os.path.join(self.cask_directory, meta_cask.depends_on_filename)
with open(path, 'r') as file:
content = file.read()

cask_name_match = self.__cask_name_pattern.search(content)
if cask_name_match is None:
return

version_match = self.__version_pattern.search(content)
if version_match is None:
return

macos_match = self.__macos_pattern.search(content)
if macos_match is None:
return

arm_match = self.__arm_pattern.search(content)
if arm_match is None:
is_arm_supported = False
else:
is_arm_supported = True

depends_on_cask_name = meta_cask.depends_on_filename.replace(".rb", "")
return DependsOnCask(
depends_on_cask_name,
version_match.group("sdk_version"),
version_match.group("runtime_version"),
cask_name_match.group("cask_name"),
macos_match.group("depends_on_macos"),
is_arm_supported)


class ReadMeUpdater:
def update(self, meta_cask: MetaCask, depends_on_cask: DependsOnCask):
file_path = 'README.md'

with open(file_path, 'r') as file:
content = file.read()

content = re.sub(rf'`{meta_cask.meta_cask_name}` \| [\d+.\d+.\d+]+', f'`{meta_cask.meta_cask_name}` | {depends_on_cask.sdk_version}', content)

with open(file_path, 'w') as file:
file.write(content)


class GitService:
def push(self, really_commit, really_push):
branch_name = 'update-meta-casks'
commit_message = '[Auto] Update meta casks'

if really_commit:
subprocess.run(['git', 'checkout', '-b', branch_name, 'master'], check = False)
subprocess.run(['git', 'add', '-A'], check = True)
subprocess.run(['git', 'commit', '-m', commit_message], check = True)

if really_push:
subprocess.run(['git', 'push', 'origin', '--force', branch_name], check = True)
subprocess.run(['gh', 'pr', 'create', '--base', 'master', '--head', branch_name, '--title', commit_message, '--body', ''], check = True)


class MetaUpdater:
def __init__(self, cask_directory: str,
meta_lookup_service: MetaLookupService,
depends_on_cask_parser: DependsOnCaskParser,
template_service: TemplateService,
read_me_updater: ReadMeUpdater):
self.cask_directory = cask_directory
self.meta_lookup_service = meta_lookup_service
self.depends_on_cask_parser = depends_on_cask_parser
self.template_service = template_service
self.read_me_updater = read_me_updater

def run(self):
meta_casks = self.meta_lookup_service.generate_version_lookup()

for meta_cask in meta_casks:
depends_on_cask = depends_on_cask_parser.parse(meta_cask)
meta_file_content = template_service.generate(meta_cask, depends_on_cask)
self.write_meta_cask(meta_cask, meta_file_content)
read_me_updater.update(meta_cask, depends_on_cask)

def write_meta_cask(self, meta_cask: MetaCask, content: str):
target_path = "{}.rb".format(os.path.join(self.cask_directory, meta_cask.meta_cask_name))
with open(target_path, "w") as file:
file.write(content)


if __name__ == '__main__':
cask_directory = os.path.join(os.path.dirname(__file__), "Casks")

meta_lookup_service = MetaLookupService(cask_directory)
depends_on_cask_parser = DependsOnCaskParser(cask_directory)
template_service = TemplateService()
read_me_updater = ReadMeUpdater()

meta_updater = MetaUpdater(cask_directory, meta_lookup_service, depends_on_cask_parser, template_service, read_me_updater)
meta_updater.run()

parser = argparse.ArgumentParser()
parser.add_argument("--really_commit", action='store_true', default=False, help='Indicates whether we really commit to git or not')
parser.add_argument("--really_push", action='store_true', default=False, help='Indicates whether we really push to remote or not')

args = parser.parse_args()

git_service = GitService()
git_service.push(args.really_commit, args.really_push)

16 changes: 16 additions & 0 deletions meta-cask.rb.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
cask "{cask_name}" do
arch arm: "arm64", intel: "x64"

version "{sdk_version},{runtime_version}"
sha256 :no_check

url "https://github.com/isen-ng/homebrew-dotnet-sdk-versions/raw/master/META.md"
name ".NET Core SDK #{version.csv.first}"
desc "This cask follows releases from https://github.com/dotnet/core/tree/master"
homepage "https://github.com/isen-ng/homebrew-dotnet-sdk-versions"

depends_on cask: "{depends_on_cask}"
depends_on macos: "{depends_on_macos}"

stage_only true
end
Loading
Loading