Current section
Files
Jump to
Current section
Files
mix.exs
defmodule Latu.ML.MixProject do
use Mix.Project
@version "0.1.0"
@source_url "https://github.com/zero-one-group/latu_ml"
def project do
[
app: :latu_ml,
version: @version,
elixir: "~> 1.20",
start_permanent: Mix.env() == :prod,
# Latu's reason: any file the incremental compiler touches is held to it, rather than
# forcing a full recompile per run with `--force --warnings-as-errors`.
elixirc_options: elixirc_options(Mix.env()),
# test/support is loaded by test_helper.exs, not by the test loader.
test_ignore_filters: [&String.starts_with?(&1, "test/support/")],
deps: deps(),
aliases: aliases(),
description: "Spark MLlib from Elixir, over Spark Connect. A companion package to Latu.",
package: package(),
docs: docs(),
source_url: @source_url
]
end
defp elixirc_options(:prod), do: []
defp elixirc_options(_env), do: [warnings_as_errors: true]
# `check` and `check.all` run `mix test`, which refuses to run outside the test env.
def cli do
[preferred_envs: [check: :test, "check.all": :test]]
end
def application do
[extra_applications: [:logger]]
end
defp deps do
[
{:latu, "~> 0.3"},
# Hard, not optional: a fitted model's attributes come back as tensors and there is no
# useful surface here without them. Roadmap section 6.
{:nx, "~> 0.13"},
{:ex_doc, "~> 0.36", only: :dev, runtime: false}
]
end
defp package do
[
licenses: ["Apache-2.0"],
links: %{
"GitHub" => @source_url,
"Latu" => "https://hexdocs.pm/latu",
"MLlib" => "https://spark.apache.org/docs/latest/ml-guide.html"
},
# `priv/` is not optional: the registries are read at compile time, so a package without
# them does not build. `usage-rules.md` ships because tooling reads it from the dep;
# `assets/` does not, because nothing in the package reads it.
files: ~w(lib priv .formatter.exs mix.exs README.md LICENSE CHANGELOG.md
usage-rules.md)
]
end
defp docs do
[
name: "Latu ML",
main: "readme",
# The README opens with the lockup image, not an `# Latu ML` heading, so ExDoc is told the
# title. The tile is the sidebar logo and the favicon; assets/README.md has the system.
logo: "assets/latu-ml-avatar.svg",
favicon: "assets/latu-ml-avatar.svg",
assets: %{"assets" => "assets"},
# Sidebar order. The guides run from tour to reference; `test/latu/docs_test.exs` checks
# that this list and `docs/guides/` agree, in both directions.
extras: [
{"README.md", title: "Latu ML"},
"docs/guides/quick-start.md",
"docs/guides/cookbook.md",
"docs/guides/from-pyspark-ml.md",
"docs/guides/with-scholar-and-nx.md",
"docs/cheatsheet.cheatmd",
"usage-rules.md",
"docs/deviations.md",
"CHANGELOG.md",
"CONTRIBUTING.md"
],
# Guides are matched by directory rather than listed, so the wildcard in `docs_test.exs`
# is the list. Reference is named: its order is the sidebar's and there is no directory
# that means "reference".
groups_for_extras: [
Guides: ~r/docs\/guides\//,
Reference: ["docs/cheatsheet.cheatmd", "usage-rules.md", "docs/deviations.md"]
],
# Two knobs, not interchangeable, and Latu's `mix.exs` explains why: prose references
# consult `skip_code_autolink_to` with the **term**; typespecs consult only
# `skip_undefined_reference_warnings_on`, by module — never the term. So a spec naming a
# generated protocol type can only be silenced per module.
#
# `Latu.Protocol.*` and `Latu.Client` are `@moduledoc false` in Latu, on purpose. Public
# specs name `Latu.ML.Plan`'s own type aliases instead, so the only place a protocol type
# is still written out is where those aliases are *defined*.
#
# The `@moduledoc false` modules **this package's own docs name** are here too, and they
# are named on purpose: `CONTRIBUTING.md` explains the generator and the golden harness,
# which means writing `Latu.ML.Generate` and `Latu.ML.Internal` down. ExDoc has no page to
# link and says so once per mention. A prose reference is checked by
# `test/latu/examples_test.exs` either way, which resolves against real exports rather
# than against what is documented.
skip_code_autolink_to: fn term ->
String.starts_with?(term, "Latu.Protocol.") or String.starts_with?(term, "Latu.Client") or
String.starts_with?(term, "Latu.ML.Generate") or
String.starts_with?(term, "Latu.ML.Internal")
end,
# What this costs, stated plainly: an undefined *type* in a spec on this one module would
# not be reported here. `elixirc_options`' `warnings_as_errors` still catches it — an
# unknown type in a `@spec` is a compile warning.
skip_undefined_reference_warnings_on: fn id -> id == "Latu.ML.Plan" end,
warnings_as_errors: true,
groups_for_modules: [
# `Latu.ML.Stat` and `Latu.ML.Functions` are verb modules too, in PySpark's own
# grouping rather than on the facade: three statistical tests that run where the data
# is, and the two column functions that convert a `Vector` to an array and back.
Verbs: [Latu.ML, Latu.ML.Functions, Latu.ML.Stat],
# The seven generated constructor modules, one per PySpark group.
Operators: [
Latu.ML.Classification,
Latu.ML.Clustering,
Latu.ML.Evaluation,
Latu.ML.Feature,
Latu.ML.FPM,
Latu.ML.Recommendation,
Latu.ML.Regression
],
# Sixty-four generated modules: one per fitted-model class (42) and one per summary
# class (22), the two counts `dev/check_offline.exs` asserts.
# Matched by shape rather than listed: they are generated, so a list here would be a
# second place to keep in step with `priv/ml_attributes.exs`. The pattern is
# deliberately narrow — a group module itself has no second dot after `Latu.ML.`.
"Model and summary attributes": [~r/^Latu\.ML\.\w+\.\w+$/],
# Inert data a verb takes or returns. You read these; you rarely name one.
"Plans and carriers": [
Latu.ML.Estimator,
Latu.ML.Transformer,
Latu.ML.Evaluator,
Latu.ML.Helper,
Latu.ML.CrossValidator,
Latu.ML.CrossValidatorModel,
Latu.ML.Model,
Latu.ML.Pipeline,
Latu.ML.PipelineModel,
Latu.ML.Summary,
Latu.ML.TrainValidationSplit,
Latu.ML.TrainValidationSplitModel,
Latu.ML.Operator,
Latu.ML.Param,
Latu.ML.SparseVector,
Latu.ML.Plan
],
Results: [Latu.ML.Linalg, Latu.ML.Result]
],
source_url: @source_url,
source_ref: "v#{@version}"
]
end
defp aliases do
[
# `--warnings-as-errors` because `mix test` compiles test files itself, outside
# `elixirc_options`; without it a warning in test/ is the one kind CI lets through.
check: [
"format --check-formatted",
"compile",
"test --warnings-as-errors"
],
# `mix docs` is spawned through `env` because this alias resolves under `:test` (the
# `preferred_envs` above) and `ex_doc` is `only: :dev`.
"check.all": [
"format --check-formatted",
"compile",
"test --include integration --warnings-as-errors",
"cmd env MIX_ENV=dev mix docs"
]
]
end
end